summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.c101
-rw-r--r--src/app.h5
-rw-r--r--src/periodic.c2
-rw-r--r--src/ui/documentwidget.c4
-rw-r--r--src/ui/inputwidget.c4
-rw-r--r--src/ui/lookupwidget.c4
-rw-r--r--src/ui/paint.c4
-rw-r--r--src/ui/root.c48
-rw-r--r--src/ui/root.h1
-rw-r--r--src/ui/sidebarwidget.c8
-rw-r--r--src/ui/touch.c20
-rw-r--r--src/ui/util.c18
-rw-r--r--src/ui/widget.c93
-rw-r--r--src/ui/widget.h6
-rw-r--r--src/ui/window.c176
-rw-r--r--src/ui/window.h5
16 files changed, 331 insertions, 168 deletions
diff --git a/src/app.c b/src/app.c
index 9ef49f4f..39f399d1 100644
--- a/src/app.c
+++ b/src/app.c
@@ -168,6 +168,7 @@ const iString *dateStr_(const iDate *date) {
168 168
169static iString *serializePrefs_App_(const iApp *d) { 169static iString *serializePrefs_App_(const iApp *d) {
170 iString *str = new_String(); 170 iString *str = new_String();
171 setCurrent_Root(d->window->roots[0]); /* TODO: How about the other? */
171 const iSidebarWidget *sidebar = findWidget_App("sidebar"); 172 const iSidebarWidget *sidebar = findWidget_App("sidebar");
172 const iSidebarWidget *sidebar2 = findWidget_App("sidebar2"); 173 const iSidebarWidget *sidebar2 = findWidget_App("sidebar2");
173#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 174#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
@@ -374,6 +375,11 @@ static void savePrefs_App_(const iApp *d) {
374static const char *magicState_App_ = "lgL1"; 375static const char *magicState_App_ = "lgL1";
375static const char *magicTabDocument_App_ = "tabd"; 376static const char *magicTabDocument_App_ = "tabd";
376 377
378enum iDocumentStateFlag {
379 current_DocumentStateFlag = iBit(1),
380 rootIndex1_DocumentStateFlag = iBit(2)
381};
382
377static iBool loadState_App_(iApp *d) { 383static iBool loadState_App_(iApp *d) {
378 iUnused(d); 384 iUnused(d);
379 const char *oldPath = concatPath_CStr(dataDir_App_(), oldStateFileName_App_); 385 const char *oldPath = concatPath_CStr(dataDir_App_(), oldStateFileName_App_);
@@ -392,17 +398,18 @@ static iBool loadState_App_(iApp *d) {
392 printf("%s: unsupported version\n", cstr_String(path_File(f))); 398 printf("%s: unsupported version\n", cstr_String(path_File(f)));
393 return iFalse; 399 return iFalse;
394 } 400 }
395 setCurrent_Root(&d->window->root);
396 setVersion_Stream(stream_File(f), version); 401 setVersion_Stream(stream_File(f), version);
397 iDocumentWidget *doc = document_App(); 402 iDocumentWidget *doc = document_App(); /* first one is always from root 0 */
398 iDocumentWidget *current = NULL; 403 iDocumentWidget *current = NULL;
399 while (!atEnd_File(f)) { 404 while (!atEnd_File(f)) {
400 readData_File(f, 4, magic); 405 readData_File(f, 4, magic);
401 if (!memcmp(magic, magicTabDocument_App_, 4)) { 406 if (!memcmp(magic, magicTabDocument_App_, 4)) {
407 const int8_t flags = read8_File(f);
402 if (!doc) { 408 if (!doc) {
409 setCurrent_Root(d->window->roots[flags & rootIndex1_DocumentStateFlag ? 1 : 0]);
403 doc = newTab_App(NULL, iFalse /* no switching */); 410 doc = newTab_App(NULL, iFalse /* no switching */);
404 } 411 }
405 if (read8_File(f)) { 412 if (flags & current_DocumentStateFlag) {
406 current = doc; 413 current = doc;
407 } 414 }
408 deserializeState_DocumentWidget(doc, stream_File(f)); 415 deserializeState_DocumentWidget(doc, stream_File(f));
@@ -421,17 +428,6 @@ static iBool loadState_App_(iApp *d) {
421 return iFalse; 428 return iFalse;
422} 429}
423 430
424iObjectList *listDocuments_App(void) {
425 iObjectList *docs = new_ObjectList();
426 const iWidget *tabs = findWidget_App("doctabs");
427 iForEach(ObjectList, i, children_Widget(findChild_Widget(tabs, "tabs.pages"))) {
428 if (isInstance_Object(i.object, &Class_DocumentWidget)) {
429 pushBack_ObjectList(docs, i.object);
430 }
431 }
432 return docs;
433}
434
435static void saveState_App_(const iApp *d) { 431static void saveState_App_(const iApp *d) {
436 iUnused(d); 432 iUnused(d);
437 trimCache_App(); 433 trimCache_App();
@@ -439,14 +435,17 @@ static void saveState_App_(const iApp *d) {
439 if (open_File(f, writeOnly_FileMode)) { 435 if (open_File(f, writeOnly_FileMode)) {
440 writeData_File(f, magicState_App_, 4); 436 writeData_File(f, magicState_App_, 4);
441 writeU32_File(f, latest_FileVersion); /* version */ 437 writeU32_File(f, latest_FileVersion); /* version */
442 setCurrent_Root(&d->window->root); 438 iConstForEach(ObjectList, i, iClob(listDocuments_App(NULL))) {
443 iConstForEach(ObjectList, i, iClob(listDocuments_App())) {
444 iAssert(isInstance_Object(i.object, &Class_DocumentWidget)); 439 iAssert(isInstance_Object(i.object, &Class_DocumentWidget));
440 const iWidget *widget = constAs_Widget(i.object);
445 writeData_File(f, magicTabDocument_App_, 4); 441 writeData_File(f, magicTabDocument_App_, 4);
446 write8_File(f, document_App() == i.object ? 1 : 0); 442 int8_t flags = (document_Root(widget->root) == i.object ? current_DocumentStateFlag : 0);
443 if (widget->root == d->window->roots[1]) {
444 flags |= rootIndex1_DocumentStateFlag;
445 }
446 write8_File(f, flags);
447 serializeState_DocumentWidget(i.object, stream_File(f)); 447 serializeState_DocumentWidget(i.object, stream_File(f));
448 } 448 }
449 setCurrent_Root(NULL);
450 } 449 }
451 else { 450 else {
452 fprintf(stderr, "[App] failed to save state: %s\n", strerror(errno)); 451 fprintf(stderr, "[App] failed to save state: %s\n", strerror(errno));
@@ -823,7 +822,7 @@ const iString *debugInfo_App(void) {
823 iString *msg = collectNew_String(); 822 iString *msg = collectNew_String();
824 format_String(msg, "# Debug information\n"); 823 format_String(msg, "# Debug information\n");
825 appendFormat_String(msg, "## Documents\n"); 824 appendFormat_String(msg, "## Documents\n");
826 iForEach(ObjectList, k, iClob(listDocuments_App())) { 825 iForEach(ObjectList, k, iClob(listDocuments_App(NULL))) {
827 iDocumentWidget *doc = k.object; 826 iDocumentWidget *doc = k.object;
828 appendFormat_String(msg, "### Tab %zu: %s\n", 827 appendFormat_String(msg, "### Tab %zu: %s\n",
829 childIndex_Widget(constAs_Widget(doc)->parent, k.object), 828 childIndex_Widget(constAs_Widget(doc)->parent, k.object),
@@ -849,7 +848,7 @@ const iString *debugInfo_App(void) {
849} 848}
850 849
851static void clearCache_App_(void) { 850static void clearCache_App_(void) {
852 iForEach(ObjectList, i, iClob(listDocuments_App())) { 851 iForEach(ObjectList, i, iClob(listDocuments_App(NULL))) {
853 clearCache_History(history_DocumentWidget(i.object)); 852 clearCache_History(history_DocumentWidget(i.object));
854 } 853 }
855} 854}
@@ -858,7 +857,7 @@ void trimCache_App(void) {
858 iApp *d = &app_; 857 iApp *d = &app_;
859 size_t cacheSize = 0; 858 size_t cacheSize = 0;
860 const size_t limit = d->prefs.maxCacheSize * 1000000; 859 const size_t limit = d->prefs.maxCacheSize * 1000000;
861 iObjectList *docs = listDocuments_App(); 860 iObjectList *docs = listDocuments_App(NULL);
862 iForEach(ObjectList, i, docs) { 861 iForEach(ObjectList, i, docs) {
863 cacheSize += cacheSize_History(history_DocumentWidget(i.object)); 862 cacheSize += cacheSize_History(history_DocumentWidget(i.object));
864 } 863 }
@@ -1051,7 +1050,12 @@ void processEvents_App(enum iAppEventMode eventMode) {
1051 handleCommand_MacOS(command_UserEvent(&ev)); 1050 handleCommand_MacOS(command_UserEvent(&ev));
1052#endif 1051#endif
1053 if (isMetricsChange_UserEvent(&ev)) { 1052 if (isMetricsChange_UserEvent(&ev)) {
1054 arrange_Widget(d->window->root.widget); 1053 iForIndices(i, d->window->roots) {
1054 iRoot *root = d->window->roots[i];
1055 if (root) {
1056 arrange_Widget(root->widget);
1057 }
1058 }
1055 } 1059 }
1056 if (!wasUsed) { 1060 if (!wasUsed) {
1057 /* No widget handled the command, so we'll do it. */ 1061 /* No widget handled the command, so we'll do it. */
@@ -1087,10 +1091,10 @@ static void runTickers_App_(iApp *d) {
1087 iSortedArray *pending = copy_SortedArray(&d->tickers); 1091 iSortedArray *pending = copy_SortedArray(&d->tickers);
1088 clear_SortedArray(&d->tickers); 1092 clear_SortedArray(&d->tickers);
1089 postRefresh_App(); 1093 postRefresh_App();
1090 setCurrent_Root(&d->window->root); /* TODO: Each ticker has its own root. */
1091 iConstForEach(Array, i, &pending->values) { 1094 iConstForEach(Array, i, &pending->values) {
1092 const iTicker *ticker = i.value; 1095 const iTicker *ticker = i.value;
1093 if (ticker->callback) { 1096 if (ticker->callback) {
1097 setCurrent_Root(findRoot_Window(d->window, ticker->context));
1094 ticker->callback(ticker->context); 1098 ticker->callback(ticker->context);
1095 } 1099 }
1096 } 1100 }
@@ -1120,7 +1124,11 @@ static int resizeWatcher_(void *user, SDL_Event *event) {
1120} 1124}
1121 1125
1122static int run_App_(iApp *d) { 1126static int run_App_(iApp *d) {
1123 arrange_Widget(findWidget_App("root")); 1127 iForIndices(i, d->window->roots) {
1128 if (d->window->roots[i]) {
1129 arrange_Widget(d->window->roots[i]->widget);
1130 }
1131 }
1124 d->isRunning = iTrue; 1132 d->isRunning = iTrue;
1125 SDL_EventState(SDL_DROPFILE, SDL_ENABLE); /* open files via drag'n'drop */ 1133 SDL_EventState(SDL_DROPFILE, SDL_ENABLE); /* open files via drag'n'drop */
1126#if defined (iPlatformDesktop) 1134#if defined (iPlatformDesktop)
@@ -1139,7 +1147,12 @@ static int run_App_(iApp *d) {
1139 1147
1140void refresh_App(void) { 1148void refresh_App(void) {
1141 iApp *d = &app_; 1149 iApp *d = &app_;
1142 destroyPending_Root(&d->window->root); 1150 iForIndices(i, d->window->roots) {
1151 iRoot *root = d->window->roots[i];
1152 if (root) {
1153 destroyPending_Root(root);
1154 }
1155 }
1143#if defined (LAGRANGE_ENABLE_IDLE_SLEEP) 1156#if defined (LAGRANGE_ENABLE_IDLE_SLEEP)
1144 if (d->warmupFrames == 0 && d->isIdling) { 1157 if (d->warmupFrames == 0 && d->isIdling) {
1145 return; 1158 return;
@@ -1270,7 +1283,16 @@ void postCommandf_App(const char *command, ...) {
1270 1283
1271iAny *findWidget_App(const char *id) { 1284iAny *findWidget_App(const char *id) {
1272 if (!*id) return NULL; 1285 if (!*id) return NULL;
1273 return findChild_Widget(app_.window->root.widget, id); 1286 iForIndices(i, app_.window->roots) {
1287 iRoot *root = app_.window->roots[i];
1288 if (root) {
1289 iAny *found = findChild_Widget(root->widget, id);
1290 if (found) {
1291 return found;
1292 }
1293 }
1294 }
1295 return NULL;
1274} 1296}
1275 1297
1276void addTicker_App(iTickerFunc ticker, iAny *context) { 1298void addTicker_App(iTickerFunc ticker, iAny *context) {
@@ -1438,8 +1460,12 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
1438 return iFalse; 1460 return iFalse;
1439} 1461}
1440 1462
1463iDocumentWidget *document_Root(iRoot *d) {
1464 return iConstCast(iDocumentWidget *, currentTabPage_Widget(findChild_Widget(d->widget, "doctabs")));
1465}
1466
1441iDocumentWidget *document_App(void) { 1467iDocumentWidget *document_App(void) {
1442 return iConstCast(iDocumentWidget *, currentTabPage_Widget(findWidget_App("doctabs"))); 1468 return document_Root(get_Root());
1443} 1469}
1444 1470
1445iDocumentWidget *document_Command(const char *cmd) { 1471iDocumentWidget *document_Command(const char *cmd) {
@@ -2128,7 +2154,7 @@ iBool handleCommand_App(const char *cmd) {
2128 else if (equal_Command(cmd, "ident.import")) { 2154 else if (equal_Command(cmd, "ident.import")) {
2129 iCertImportWidget *imp = new_CertImportWidget(); 2155 iCertImportWidget *imp = new_CertImportWidget();
2130 setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App())); 2156 setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App()));
2131 addChild_Widget(d->window->root.widget, iClob(imp)); 2157 addChild_Widget(get_Root()->widget, iClob(imp));
2132 finalizeSheet_Widget(as_Widget(imp)); 2158 finalizeSheet_Widget(as_Widget(imp));
2133 postRefresh_App(); 2159 postRefresh_App();
2134 return iTrue; 2160 return iTrue;
@@ -2173,7 +2199,7 @@ iBool handleCommand_App(const char *cmd) {
2173 iProcessId pid = argLabel_Command(cmd, "pid"); 2199 iProcessId pid = argLabel_Command(cmd, "pid");
2174 if (pid) { 2200 if (pid) {
2175 iString *urls = collectNew_String(); 2201 iString *urls = collectNew_String();
2176 iConstForEach(ObjectList, i, iClob(listDocuments_App())) { 2202 iConstForEach(ObjectList, i, iClob(listDocuments_App(NULL))) {
2177 append_String(urls, url_DocumentWidget(i.object)); 2203 append_String(urls, url_DocumentWidget(i.object));
2178 appendCStr_String(urls, "\n"); 2204 appendCStr_String(urls, "\n");
2179 } 2205 }
@@ -2258,3 +2284,20 @@ void revealPath_App(const iString *path) {
2258 iAssert(0 /* File revealing not implemented on this platform */); 2284 iAssert(0 /* File revealing not implemented on this platform */);
2259#endif 2285#endif
2260} 2286}
2287
2288iObjectList *listDocuments_App(const iRoot *rootOrNull) {
2289 iWindow *win = get_Window();
2290 iObjectList *docs = new_ObjectList();
2291 iForIndices(i, win->roots) {
2292 iRoot *root = win->roots[i];
2293 if (!rootOrNull || root == rootOrNull) {
2294 const iWidget *tabs = findChild_Widget(root->widget, "doctabs");
2295 iForEach(ObjectList, i, children_Widget(findChild_Widget(tabs, "tabs.pages"))) {
2296 if (isInstance_Object(i.object, &Class_DocumentWidget)) {
2297 pushBack_ObjectList(docs, i.object);
2298 }
2299 }
2300 }
2301 }
2302 return docs;
2303}
diff --git a/src/app.h b/src/app.h
index 9f3754cc..0012d979 100644
--- a/src/app.h
+++ b/src/app.h
@@ -36,6 +36,7 @@ iDeclareType(DocumentWidget)
36iDeclareType(GmCerts) 36iDeclareType(GmCerts)
37iDeclareType(MimeHooks) 37iDeclareType(MimeHooks)
38iDeclareType(Periodic) 38iDeclareType(Periodic)
39iDeclareType(Root)
39iDeclareType(Visited) 40iDeclareType(Visited)
40iDeclareType(Window) 41iDeclareType(Window)
41 42
@@ -86,10 +87,12 @@ iBookmarks * bookmarks_App (void);
86iMimeHooks * mimeHooks_App (void); 87iMimeHooks * mimeHooks_App (void);
87iPeriodic * periodic_App (void); 88iPeriodic * periodic_App (void);
88iDocumentWidget * document_App (void); 89iDocumentWidget * document_App (void);
89iObjectList * listDocuments_App (void); 90iObjectList * listDocuments_App (const iRoot *rootOrNull); /* NULL for all roots */
90iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew); 91iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew);
91void trimCache_App (void); 92void trimCache_App (void);
92 93
94iDocumentWidget * document_Root (iRoot *);
95
93const iPrefs * prefs_App (void); 96const iPrefs * prefs_App (void);
94iBool forceSoftwareRender_App(void); 97iBool forceSoftwareRender_App(void);
95enum iColorTheme colorTheme_App (void); 98enum iColorTheme colorTheme_App (void);
diff --git a/src/periodic.c b/src/periodic.c
index ab3b66e6..eb4fb98d 100644
--- a/src/periodic.c
+++ b/src/periodic.c
@@ -65,7 +65,6 @@ iBool dispatchCommands_Periodic(iPeriodic *d) {
65 d->lastPostTime = now; 65 d->lastPostTime = now;
66 iBool wasPosted = iFalse; 66 iBool wasPosted = iFalse;
67 lock_Mutex(d->mutex); 67 lock_Mutex(d->mutex);
68 setCurrent_Root(&get_Window()->root);
69 iConstForEach(Array, i, &d->commands.values) { 68 iConstForEach(Array, i, &d->commands.values) {
70 const iPeriodicCommand *pc = i.value; 69 const iPeriodicCommand *pc = i.value;
71 const SDL_UserEvent ev = { 70 const SDL_UserEvent ev = {
@@ -74,6 +73,7 @@ iBool dispatchCommands_Periodic(iPeriodic *d) {
74 .data1 = (void *) cstr_String(&pc->command) 73 .data1 = (void *) cstr_String(&pc->command)
75 }; 74 };
76 iAssert(isInstance_Object(pc->context, &Class_Widget)); 75 iAssert(isInstance_Object(pc->context, &Class_Widget));
76 setCurrent_Root(findRoot_Window(get_Window(), pc->context));
77 dispatchEvent_Widget(pc->context, (const SDL_Event *) &ev); 77 dispatchEvent_Widget(pc->context, (const SDL_Event *) &ev);
78 wasPosted = iTrue; 78 wasPosted = iTrue;
79 } 79 }
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index e09844c2..79678f56 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -567,7 +567,7 @@ static int scrollMax_DocumentWidget_(const iDocumentWidget *d) {
567 int sm = size_GmDocument(d->doc).y - height_Rect(bounds_Widget(constAs_Widget(d))) + 567 int sm = size_GmDocument(d->doc).y - height_Rect(bounds_Widget(constAs_Widget(d))) +
568 (hasSiteBanner_GmDocument(d->doc) ? 1 : 2) * d->pageMargin * gap_UI; 568 (hasSiteBanner_GmDocument(d->doc) ? 1 : 2) * d->pageMargin * gap_UI;
569 if (d->phoneToolbar) { 569 if (d->phoneToolbar) {
570 sm += size_Root(get_Root()).y - 570 sm += size_Root(constAs_Widget(d)->root).y -
571 top_Rect(boundsWithoutVisualOffset_Widget(d->phoneToolbar)); 571 top_Rect(boundsWithoutVisualOffset_Widget(d->phoneToolbar));
572 } 572 }
573 return sm; 573 return sm;
@@ -1354,7 +1354,7 @@ static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t du
1354 /* Show and hide toolbar on scroll. */ 1354 /* Show and hide toolbar on scroll. */
1355 if (deviceType_App() == phone_AppDeviceType) { 1355 if (deviceType_App() == phone_AppDeviceType) {
1356 if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5) { 1356 if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5) {
1357 showToolbars_Root(get_Root(), offset < 0); 1357 showToolbars_Root(as_Widget(d)->root, offset < 0);
1358 } 1358 }
1359 } 1359 }
1360 updateVisible_DocumentWidget_(d); 1360 updateVisible_DocumentWidget_(d);
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 52cb9805..9d360f47 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -338,7 +338,7 @@ void setText_InputWidget(iInputWidget *d, const iString *text) {
338 text = enc; 338 text = enc;
339 } 339 }
340 /* Omit the default (Gemini) scheme if there isn't much space. */ 340 /* Omit the default (Gemini) scheme if there isn't much space. */
341 if (isNarrow_Root(get_Root())) { // flags_Widget(as_Widget(d)) & tight_WidgetFlag) { 341 if (isNarrow_Root(as_Widget(d)->root)) {
342 text = omitDefaultScheme_(collect_String(copy_String(text))); 342 text = omitDefaultScheme_(collect_String(copy_String(text)));
343 } 343 }
344 } 344 }
@@ -733,7 +733,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
733 if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) { 733 if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) {
734 iRect rect = bounds_Widget(w); 734 iRect rect = bounds_Widget(w);
735 rect.pos.y -= value_Anim(&get_Window()->rootOffset); 735 rect.pos.y -= value_Anim(&get_Window()->rootOffset);
736 const iInt2 visRoot = visibleSize_Root(get_Root()); 736 const iInt2 visRoot = visibleSize_Root(w->root);
737 if (bottom_Rect(rect) > visRoot.y) { 737 if (bottom_Rect(rect) > visRoot.y) {
738 setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250); 738 setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250);
739 } 739 }
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c
index 7c3ebce2..d8e594de 100644
--- a/src/ui/lookupwidget.c
+++ b/src/ui/lookupwidget.c
@@ -427,7 +427,7 @@ void submit_LookupWidget(iLookupWidget *d, const iString *term) {
427 trim_String(&d->pendingTerm); 427 trim_String(&d->pendingTerm);
428 iReleasePtr(&d->pendingDocs); 428 iReleasePtr(&d->pendingDocs);
429 if (!isEmpty_String(&d->pendingTerm)) { 429 if (!isEmpty_String(&d->pendingTerm)) {
430 d->pendingDocs = listDocuments_App(); /* holds reference to all open tabs */ 430 d->pendingDocs = listDocuments_App(get_Root()); /* holds reference to all open tabs */
431 signal_Condition(&d->jobAvailable); 431 signal_Condition(&d->jobAvailable);
432 } 432 }
433 else { 433 else {
@@ -654,7 +654,7 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
654 (equal_Command(cmd, "layout.changed") && 654 (equal_Command(cmd, "layout.changed") &&
655 equal_Rangecc(range_Command(cmd, "id"), "navbar"))) { 655 equal_Rangecc(range_Command(cmd, "id"), "navbar"))) {
656 /* Position the lookup popup under the URL bar. */ { 656 /* Position the lookup popup under the URL bar. */ {
657 const iInt2 rootSize = size_Root(get_Root()); 657 const iInt2 rootSize = size_Root(w->root);
658 const iRect navBarBounds = bounds_Widget(findWidget_App("navbar")); 658 const iRect navBarBounds = bounds_Widget(findWidget_App("navbar"));
659 setFixedSize_Widget(w, init_I2(width_Widget(findWidget_App("url")), 659 setFixedSize_Widget(w, init_I2(width_Widget(findWidget_App("url")),
660 (rootSize.y - bottom_Rect(navBarBounds)) / 2)); 660 (rootSize.y - bottom_Rect(navBarBounds)) / 2));
diff --git a/src/ui/paint.c b/src/ui/paint.c
index 02914489..8843e2e2 100644
--- a/src/ui/paint.c
+++ b/src/ui/paint.c
@@ -87,8 +87,8 @@ void unsetClip_Paint(iPaint *d) {
87void drawRect_Paint(const iPaint *d, iRect rect, int color) { 87void drawRect_Paint(const iPaint *d, iRect rect, int color) {
88 iInt2 br = bottomRight_Rect(rect); 88 iInt2 br = bottomRight_Rect(rect);
89 /* Keep the right/bottom edge visible in the window. */ 89 /* Keep the right/bottom edge visible in the window. */
90 if (br.x == d->dst->root.widget->rect.size.x) br.x--; 90 if (br.x == d->dst->size.x) br.x--;
91 if (br.y == d->dst->root.widget->rect.size.y) br.y--; 91 if (br.y == d->dst->size.y) br.y--;
92 const SDL_Point edges[] = { 92 const SDL_Point edges[] = {
93 { left_Rect(rect), top_Rect(rect) }, 93 { left_Rect(rect), top_Rect(rect) },
94 { br.x, top_Rect(rect) }, 94 { br.x, top_Rect(rect) },
diff --git a/src/ui/root.c b/src/ui/root.c
index 375107c9..f91c3ba5 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -420,37 +420,37 @@ static const char *loadAnimationCStr_(void) {
420 return stopSeqCStr_[loadAnimIndex_ % iElemCount(stopSeqCStr_)]; 420 return stopSeqCStr_[loadAnimIndex_ % iElemCount(stopSeqCStr_)];
421} 421}
422 422
423static uint32_t updateReloadAnimation_Window_(uint32_t interval, void *window) { 423static uint32_t updateReloadAnimation_Root_(uint32_t interval, void *root) {
424 iUnused(window);
425 loadAnimIndex_++; 424 loadAnimIndex_++;
426 postCommand_App("window.reload.update"); 425 postCommandf_App("window.reload.update root:%p", root);
427 return interval; 426 return interval;
428} 427}
429 428
430static void setReloadLabel_Window_(iWindow *d, iBool animating) { 429static void setReloadLabel_Root_(iRoot *d, iBool animating) {
431 const iBool isMobile = deviceType_App() != desktop_AppDeviceType; 430 const iBool isMobile = deviceType_App() != desktop_AppDeviceType;
432 iLabelWidget *label = findChild_Widget(d->root.widget, "reload"); 431 iLabelWidget *label = findChild_Widget(d->widget, "reload");
433 updateTextCStr_LabelWidget(label, animating ? loadAnimationCStr_() : 432 updateTextCStr_LabelWidget(
434 (isMobile ? pageMenuCStr_ : reloadCStr_)); 433 label, animating ? loadAnimationCStr_() : (isMobile ? pageMenuCStr_ : reloadCStr_));
435 if (isMobile) { 434 if (isMobile) {
436 setCommand_LabelWidget(label, 435 setCommand_LabelWidget(label,
437 collectNewCStr_String(animating ? "navigate.reload" : "menu.open")); 436 collectNewCStr_String(animating ? "navigate.reload" : "menu.open"));
438 } 437 }
439} 438}
440 439
441static void checkLoadAnimation_Window_(iWindow *d) { 440static void checkLoadAnimation_Root_(iRoot *d) {
442 const iBool isOngoing = isRequestOngoing_DocumentWidget(document_App()); 441 const iBool isOngoing = isRequestOngoing_DocumentWidget(document_Root(d));
443 if (isOngoing && !d->loadAnimTimer) { 442 if (isOngoing && !d->loadAnimTimer) {
444 d->loadAnimTimer = SDL_AddTimer(loadAnimIntervalMs_, updateReloadAnimation_Window_, d); 443 d->loadAnimTimer = SDL_AddTimer(loadAnimIntervalMs_, updateReloadAnimation_Root_, d);
445 } 444 }
446 else if (!isOngoing && d->loadAnimTimer) { 445 else if (!isOngoing && d->loadAnimTimer) {
447 SDL_RemoveTimer(d->loadAnimTimer); 446 SDL_RemoveTimer(d->loadAnimTimer);
448 d->loadAnimTimer = 0; 447 d->loadAnimTimer = 0;
449 } 448 }
450 setReloadLabel_Window_(d, isOngoing); 449 setReloadLabel_Root_(d, isOngoing);
451} 450}
452 451
453void updatePadding_Root(iRoot *d) { 452void updatePadding_Root(iRoot *d) {
453 if (d == NULL) return;
454#if defined (iPlatformAppleMobile) 454#if defined (iPlatformAppleMobile)
455 iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); 455 iWidget *toolBar = findChild_Widget(d->widget, "toolbar");
456 float left, top, right, bottom; 456 float left, top, right, bottom;
@@ -470,8 +470,6 @@ void updatePadding_Root(iRoot *d) {
470 are not arranged correctly until it's hidden and reshown. */ 470 are not arranged correctly until it's hidden and reshown. */
471 } 471 }
472 /* Note that `handleNavBarCommands_` also adjusts padding and spacing. */ 472 /* Note that `handleNavBarCommands_` also adjusts padding and spacing. */
473#else
474 iUnused(d);
475#endif 473#endif
476} 474}
477 475
@@ -529,7 +527,7 @@ iBool isNarrow_Root(const iRoot *d) {
529static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { 527static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
530 if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "metrics.changed")) { 528 if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "metrics.changed")) {
531 const iBool isPhone = deviceType_App() == phone_AppDeviceType; 529 const iBool isPhone = deviceType_App() == phone_AppDeviceType;
532 const iBool isNarrow = !isPhone && isNarrow_Root(get_Root()); 530 const iBool isNarrow = !isPhone && isNarrow_Root(navBar->root);
533 /* Adjust navbar padding. */ { 531 /* Adjust navbar padding. */ {
534 int hPad = isPhone && isPortrait_App() ? 0 : (isPhone || isNarrow) ? gap_UI / 2 532 int hPad = isPhone && isPortrait_App() ? 0 : (isPhone || isNarrow) ? gap_UI / 2
535 : gap_UI * 3 / 2; 533 : gap_UI * 3 / 2;
@@ -570,7 +568,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
570 updateSize_LabelWidget(btn); 568 updateSize_LabelWidget(btn);
571 } 569 }
572 } 570 }
573 arrange_Widget(get_Window()->root.widget); 571 arrange_Widget(navBar->root->widget);
574 } 572 }
575 /* Resize the URL input field. */ { 573 /* Resize the URL input field. */ {
576 iWidget *urlBar = findChild_Widget(navBar, "url"); 574 iWidget *urlBar = findChild_Widget(navBar, "url");
@@ -582,8 +580,11 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
582 return iFalse; 580 return iFalse;
583 } 581 }
584 else if (equal_Command(cmd, "window.reload.update")) { 582 else if (equal_Command(cmd, "window.reload.update")) {
585 checkLoadAnimation_Window_(get_Window()); 583 if (pointerLabel_Command(cmd, "root") == get_Root()) {
586 return iTrue; 584 checkLoadAnimation_Root_(get_Root());
585 return iTrue;
586 }
587 return iFalse;
587 } 588 }
588 else if (equal_Command(cmd, "navigate.focus")) { 589 else if (equal_Command(cmd, "navigate.focus")) {
589 iWidget *url = findChild_Widget(navBar, "url"); 590 iWidget *url = findChild_Widget(navBar, "url");
@@ -638,7 +639,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
638 visitUrl_Visited(visited_App(), withSpacesEncoded_String(urlStr), 0); /* TODO: internal URI normalization */ 639 visitUrl_Visited(visited_App(), withSpacesEncoded_String(urlStr), 0); /* TODO: internal URI normalization */
639 postCommand_App("visited.changed"); /* sidebar will update */ 640 postCommand_App("visited.changed"); /* sidebar will update */
640 setText_InputWidget(url, urlStr); 641 setText_InputWidget(url, urlStr);
641 checkLoadAnimation_Window_(get_Window()); 642 checkLoadAnimation_Root_(get_Root());
642 dismissPortraitPhoneSidebars_Root(get_Root()); 643 dismissPortraitPhoneSidebars_Root(get_Root());
643 updateNavBarIdentity_(navBar); 644 updateNavBarIdentity_(navBar);
644 updateNavDirButtons_(navBar); 645 updateNavDirButtons_(navBar);
@@ -651,13 +652,13 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
651 return iFalse; 652 return iFalse;
652 } 653 }
653 else if (equal_Command(cmd, "document.request.cancelled")) { 654 else if (equal_Command(cmd, "document.request.cancelled")) {
654 checkLoadAnimation_Window_(get_Window()); 655 checkLoadAnimation_Root_(get_Root());
655 return iFalse; 656 return iFalse;
656 } 657 }
657 else if (equal_Command(cmd, "document.request.started")) { 658 else if (equal_Command(cmd, "document.request.started")) {
658 iInputWidget *url = findChild_Widget(navBar, "url"); 659 iInputWidget *url = findChild_Widget(navBar, "url");
659 setTextCStr_InputWidget(url, suffixPtr_Command(cmd, "url")); 660 setTextCStr_InputWidget(url, suffixPtr_Command(cmd, "url"));
660 checkLoadAnimation_Window_(get_Window()); 661 checkLoadAnimation_Root_(get_Root());
661 dismissPortraitPhoneSidebars_Root(get_Root()); 662 dismissPortraitPhoneSidebars_Root(get_Root());
662 return iFalse; 663 return iFalse;
663 } 664 }
@@ -668,7 +669,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
668 iDocumentWidget *doc = document_App(); 669 iDocumentWidget *doc = document_App();
669 if (doc) { 670 if (doc) {
670 setText_InputWidget(findChild_Widget(navBar, "url"), url_DocumentWidget(doc)); 671 setText_InputWidget(findChild_Widget(navBar, "url"), url_DocumentWidget(doc));
671 checkLoadAnimation_Window_(get_Window()); 672 checkLoadAnimation_Root_(get_Root());
672 updateNavBarIdentity_(navBar); 673 updateNavBarIdentity_(navBar);
673 } 674 }
674 setFocus_Widget(NULL); 675 setFocus_Widget(NULL);
@@ -722,7 +723,7 @@ static iBool handleSearchBarCommands_(iWidget *searchBar, const char *cmd) {
722 if (pointer_Command(cmd) == findChild_Widget(searchBar, "find.input")) { 723 if (pointer_Command(cmd) == findChild_Widget(searchBar, "find.input")) {
723 if (!isVisible_Widget(searchBar)) { 724 if (!isVisible_Widget(searchBar)) {
724 setFlags_Widget(searchBar, hidden_WidgetFlag | disabled_WidgetFlag, iFalse); 725 setFlags_Widget(searchBar, hidden_WidgetFlag | disabled_WidgetFlag, iFalse);
725 arrange_Widget(get_Window()->root.widget); 726 arrange_Widget(root_Widget(searchBar));
726 postRefresh_App(); 727 postRefresh_App();
727 } 728 }
728 } 729 }
@@ -831,6 +832,9 @@ static int appIconSize_(void) {
831} 832}
832 833
833void updateMetrics_Root(iRoot *d) { 834void updateMetrics_Root(iRoot *d) {
835 if (d == NULL) {
836 return;
837 }
834 /* Custom frame. */ 838 /* Custom frame. */
835 iWidget *winBar = findChild_Widget(d->widget, "winbar"); 839 iWidget *winBar = findChild_Widget(d->widget, "winbar");
836 if (winBar) { 840 if (winBar) {
diff --git a/src/ui/root.h b/src/ui/root.h
index 65da8d85..39e7bf3c 100644
--- a/src/ui/root.h
+++ b/src/ui/root.h
@@ -13,6 +13,7 @@ struct Impl_Root {
13 iWidget * focus; 13 iWidget * focus;
14 iPtrArray *onTop; /* order is important; last one is topmost */ 14 iPtrArray *onTop; /* order is important; last one is topmost */
15 iPtrSet * pendingDestruction; 15 iPtrSet * pendingDestruction;
16 int loadAnimTimer;
16}; 17};
17 18
18iDeclareTypeConstruction(Root) 19iDeclareTypeConstruction(Root)
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index f31dd195..62d195fb 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -709,7 +709,7 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, const iSidebarItem *it
709 const iGmDocument *doc = document_DocumentWidget(document_App()); 709 const iGmDocument *doc = document_DocumentWidget(document_App());
710 const iGmHeading *head = constAt_Array(headings_GmDocument(doc), item->id); 710 const iGmHeading *head = constAt_Array(headings_GmDocument(doc), item->id);
711 postCommandf_App("document.goto loc:%p", head->text.start); 711 postCommandf_App("document.goto loc:%p", head->text.start);
712 dismissPortraitPhoneSidebars_Root(get_Root()); 712 dismissPortraitPhoneSidebars_Root(as_Widget(d)->root);
713 break; 713 break;
714 } 714 }
715 case feeds_SidebarMode: { 715 case feeds_SidebarMode: {
@@ -792,7 +792,7 @@ void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) {
792 /* Even less space if the other sidebar is visible, too. */ 792 /* Even less space if the other sidebar is visible, too. */
793 const int otherWidth = 793 const int otherWidth =
794 width_Widget(findWidget_App(d->side == left_SideBarSide ? "sidebar2" : "sidebar")); 794 width_Widget(findWidget_App(d->side == left_SideBarSide ? "sidebar2" : "sidebar"));
795 width = iClamp(width, 30 * gap_UI, size_Root(get_Root()).x - 50 * gap_UI - otherWidth); 795 width = iClamp(width, 30 * gap_UI, size_Root(w->root).x - 50 * gap_UI - otherWidth);
796 } 796 }
797 d->widthAsGaps = (float) width / (float) gap_UI; 797 d->widthAsGaps = (float) width / (float) gap_UI;
798 if (isVisible_Widget(w)) { 798 if (isVisible_Widget(w)) {
@@ -967,7 +967,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
967 d, 967 d,
968 ((d->side == left_SideBarSide 968 ((d->side == left_SideBarSide
969 ? local.x 969 ? local.x
970 : (size_Root(get_Root()).x - coord_Command(cmd).x)) + 970 : (size_Root(w->root).x - coord_Command(cmd).x)) +
971 resMid) / (float) gap_UI); 971 resMid) / (float) gap_UI);
972 } 972 }
973 return iTrue; 973 return iTrue;
@@ -1170,7 +1170,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1170 else if (isCommand_Widget(w, ev, "ident.edit")) { 1170 else if (isCommand_Widget(w, ev, "ident.edit")) {
1171 const iGmIdentity *ident = menuIdentity_SidebarWidget_(d); 1171 const iGmIdentity *ident = menuIdentity_SidebarWidget_(d);
1172 if (ident) { 1172 if (ident) {
1173 makeValueInput_Widget(get_Window()->root.widget, 1173 makeValueInput_Widget(get_Root()->widget,
1174 &ident->notes, 1174 &ident->notes,
1175 uiHeading_ColorEscape "${heading.ident.notes}", 1175 uiHeading_ColorEscape "${heading.ident.notes}",
1176 format_CStr(cstr_Lang("dlg.ident.notes"), cstr_String(name_GmIdentity(ident))), 1176 format_CStr(cstr_Lang("dlg.ident.notes"), cstr_String(name_GmIdentity(ident))),
diff --git a/src/ui/touch.c b/src/ui/touch.c
index 6867a34b..e49fce2a 100644
--- a/src/ui/touch.c
+++ b/src/ui/touch.c
@@ -161,7 +161,7 @@ static iBool clearWidgetMomentum_TouchState_(iTouchState *d, iWidget *widget) {
161 161
162static void dispatchMotion_Touch_(iFloat3 pos, int buttonState) { 162static void dispatchMotion_Touch_(iFloat3 pos, int buttonState) {
163 touchState_()->currentTouchPos = initF3_I2(pos); 163 touchState_()->currentTouchPos = initF3_I2(pos);
164 dispatchEvent_Widget(get_Window()->root.widget, (SDL_Event *) &(SDL_MouseMotionEvent){ 164 dispatchEvent_Window(get_Window(), (SDL_Event *) &(SDL_MouseMotionEvent){
165 .type = SDL_MOUSEMOTION, 165 .type = SDL_MOUSEMOTION,
166 .timestamp = SDL_GetTicks(), 166 .timestamp = SDL_GetTicks(),
167 .which = SDL_TOUCH_MOUSEID, 167 .which = SDL_TOUCH_MOUSEID,
@@ -185,12 +185,12 @@ static iBool dispatchClick_Touch_(const iTouch *d, int button) {
185 .x = x_F3(tapPos), 185 .x = x_F3(tapPos),
186 .y = y_F3(tapPos) 186 .y = y_F3(tapPos)
187 }; 187 };
188 iBool wasUsed = dispatchEvent_Widget(window->root.widget, (SDL_Event *) &btn); 188 iBool wasUsed = dispatchEvent_Window(window, (SDL_Event *) &btn);
189 /* Immediately released, too. */ 189 /* Immediately released, too. */
190 btn.type = SDL_MOUSEBUTTONUP; 190 btn.type = SDL_MOUSEBUTTONUP;
191 btn.state = SDL_RELEASED; 191 btn.state = SDL_RELEASED;
192 btn.timestamp = SDL_GetTicks(); 192 btn.timestamp = SDL_GetTicks();
193 dispatchEvent_Widget(window->root.widget, (SDL_Event *) &btn); 193 dispatchEvent_Window(window, (SDL_Event *) &btn);
194 if (!wasUsed && button == SDL_BUTTON_RIGHT) { 194 if (!wasUsed && button == SDL_BUTTON_RIGHT) {
195 postContextClick_Window(window, &btn); 195 postContextClick_Window(window, &btn);
196 } 196 }
@@ -199,7 +199,7 @@ static iBool dispatchClick_Touch_(const iTouch *d, int button) {
199 199
200static void dispatchButtonDown_Touch_(iFloat3 pos) { 200static void dispatchButtonDown_Touch_(iFloat3 pos) {
201 touchState_()->currentTouchPos = initF3_I2(pos); 201 touchState_()->currentTouchPos = initF3_I2(pos);
202 dispatchEvent_Widget(get_Window()->root.widget, (SDL_Event *) &(SDL_MouseButtonEvent){ 202 dispatchEvent_Window(get_Window(), (SDL_Event *) &(SDL_MouseButtonEvent){
203 .type = SDL_MOUSEBUTTONDOWN, 203 .type = SDL_MOUSEBUTTONDOWN,
204 .timestamp = SDL_GetTicks(), 204 .timestamp = SDL_GetTicks(),
205 .clicks = 1, 205 .clicks = 1,
@@ -213,7 +213,7 @@ static void dispatchButtonDown_Touch_(iFloat3 pos) {
213 213
214static void dispatchButtonUp_Touch_(iFloat3 pos) { 214static void dispatchButtonUp_Touch_(iFloat3 pos) {
215 touchState_()->currentTouchPos = initF3_I2(pos); 215 touchState_()->currentTouchPos = initF3_I2(pos);
216 dispatchEvent_Widget(get_Window()->root.widget, (SDL_Event *) &(SDL_MouseButtonEvent){ 216 dispatchEvent_Window(get_Window(), (SDL_Event *) &(SDL_MouseButtonEvent){
217 .type = SDL_MOUSEBUTTONUP, 217 .type = SDL_MOUSEBUTTONUP,
218 .timestamp = SDL_GetTicks(), 218 .timestamp = SDL_GetTicks(),
219 .clicks = 1, 219 .clicks = 1,
@@ -227,12 +227,15 @@ static void dispatchButtonUp_Touch_(iFloat3 pos) {
227 227
228static void dispatchNotification_Touch_(const iTouch *d, int code) { 228static void dispatchNotification_Touch_(const iTouch *d, int code) {
229 if (d->affinity) { 229 if (d->affinity) {
230 iRoot *oldRoot = get_Root();
231 setCurrent_Root(d->affinity->root);
230 dispatchEvent_Widget(d->affinity, (SDL_Event *) &(SDL_UserEvent){ 232 dispatchEvent_Widget(d->affinity, (SDL_Event *) &(SDL_UserEvent){
231 .type = SDL_USEREVENT, 233 .type = SDL_USEREVENT,
232 .timestamp = SDL_GetTicks(), 234 .timestamp = SDL_GetTicks(),
233 .code = code, 235 .code = code,
234 .data1 = d->affinity 236 .data1 = d->affinity
235 }); 237 });
238 setCurrent_Root(oldRoot);
236 } 239 }
237} 240}
238 241
@@ -335,7 +338,7 @@ static void update_TouchState_(void *ptr) {
335} 338}
336 339
337static iWidget *findOverflowScrollable_Widget_(iWidget *d) { 340static iWidget *findOverflowScrollable_Widget_(iWidget *d) {
338 const iInt2 rootSize = size_Root(get_Root()); 341 const iInt2 rootSize = size_Root(d->root);
339 for (iWidget *w = d; w; w = parent_Widget(w)) { 342 for (iWidget *w = d; w; w = parent_Widget(w)) {
340 if (flags_Widget(w) & overflowScrollable_WidgetFlag) { 343 if (flags_Widget(w) & overflowScrollable_WidgetFlag) {
341 if (height_Widget(w) > rootSize.y && !hasVisibleChildOnTop_Widget(w)) { 344 if (height_Widget(w) > rootSize.y && !hasVisibleChildOnTop_Widget(w)) {
@@ -441,7 +444,7 @@ iBool processEvent_Touch(const SDL_Event *ev) {
441 if (!isFinished_Anim(&window->rootOffset)) { 444 if (!isFinished_Anim(&window->rootOffset)) {
442 return iFalse; 445 return iFalse;
443 } 446 }
444 const iInt2 rootSize = size_Root(get_Root()); 447 const iInt2 rootSize = size_Window(window);
445 const SDL_TouchFingerEvent *fing = &ev->tfinger; 448 const SDL_TouchFingerEvent *fing = &ev->tfinger;
446 const iFloat3 pos = add_F3(init_F3(fing->x * rootSize.x, fing->y * rootSize.y, 0), /* pixels */ 449 const iFloat3 pos = add_F3(init_F3(fing->x * rootSize.x, fing->y * rootSize.y, 0), /* pixels */
447 init_F3(0, -value_Anim(&window->rootOffset), 0)); 450 init_F3(0, -value_Anim(&window->rootOffset), 0));
@@ -458,7 +461,7 @@ iBool processEvent_Touch(const SDL_Event *ev) {
458 else if (x > rootSize.x - edgeWidth) { 461 else if (x > rootSize.x - edgeWidth) {
459 edge = right_TouchEdge; 462 edge = right_TouchEdge;
460 } 463 }
461 iWidget *aff = hitChild_Widget(window->root.widget, init_I2(iRound(x), iRound(y_F3(pos)))); 464 iWidget *aff = hitChild_Window(window, init_I2(iRound(x), iRound(y_F3(pos))));
462 if (edge == left_TouchEdge) { 465 if (edge == left_TouchEdge) {
463 dragging = findSlidePanel_Widget_(aff); 466 dragging = findSlidePanel_Widget_(aff);
464 if (dragging) { 467 if (dragging) {
@@ -607,7 +610,6 @@ iBool processEvent_Touch(const SDL_Event *ev) {
607 } 610 }
608 } 611 }
609 else if (ev->type == SDL_FINGERUP) { 612 else if (ev->type == SDL_FINGERUP) {
610 iTouch *touch = find_TouchState_(d, fing->fingerId);
611 iForEach(Array, i, d->touches) { 613 iForEach(Array, i, d->touches) {
612 iTouch *touch = i.value; 614 iTouch *touch = i.value;
613 if (touch->id != fing->fingerId) { 615 if (touch->id != fing->fingerId) {
diff --git a/src/ui/util.c b/src/ui/util.c
index 2a72d511..edfd43cc 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -635,7 +635,7 @@ static iBool menuHandler_(iWidget *menu, const char *cmd) {
635 } 635 }
636 if ((equal_Command(cmd, "mouse.clicked") || equal_Command(cmd, "mouse.missed")) && 636 if ((equal_Command(cmd, "mouse.clicked") || equal_Command(cmd, "mouse.missed")) &&
637 arg_Command(cmd)) { 637 arg_Command(cmd)) {
638 if (hitChild_Widget(get_Window()->root.widget, coord_Command(cmd)) == parentMenuButton_(menu)) { 638 if (hitChild_Window(get_Window(), coord_Command(cmd)) == parentMenuButton_(menu)) {
639 return iFalse; 639 return iFalse;
640 } 640 }
641 /* Dismiss open menus when clicking outside them. */ 641 /* Dismiss open menus when clicking outside them. */
@@ -1366,7 +1366,7 @@ void finalizeSheet_Widget(iWidget *sheet) {
1366 easier to create phone versions of each dialog, but at least this works with any 1366 easier to create phone versions of each dialog, but at least this works with any
1367 future changes to the UI (..."works"). At least this way it is possible to enforce 1367 future changes to the UI (..."works"). At least this way it is possible to enforce
1368 a consistent styling. */ 1368 a consistent styling. */
1369 if (deviceType_App() == phone_AppDeviceType && parent_Widget(sheet) == get_Window()->root.widget) { 1369 if (deviceType_App() == phone_AppDeviceType && parent_Widget(sheet) == root_Widget(sheet)) {
1370 if (~flags_Widget(sheet) & keepOnTop_WidgetFlag) { 1370 if (~flags_Widget(sheet) & keepOnTop_WidgetFlag) {
1371 /* Already finalized. */ 1371 /* Already finalized. */
1372 arrange_Widget(sheet); 1372 arrange_Widget(sheet);
@@ -1573,7 +1573,7 @@ void finalizeSheet_Widget(iWidget *sheet) {
1573 destroy_Widget(pageContent); 1573 destroy_Widget(pageContent);
1574 setFlags_Widget(owner, drawBackgroundToBottom_WidgetFlag, iTrue); 1574 setFlags_Widget(owner, drawBackgroundToBottom_WidgetFlag, iTrue);
1575 } 1575 }
1576 destroyPending_Root(get_Root()); 1576 destroyPending_Root(sheet->root);
1577 /* Additional elements for preferences. */ 1577 /* Additional elements for preferences. */
1578 if (isPrefs) { 1578 if (isPrefs) {
1579 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); 1579 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId))));
@@ -1754,7 +1754,7 @@ static void acceptValueInput_(iWidget *dlg) {
1754} 1754}
1755 1755
1756static void updateValueInputWidth_(iWidget *dlg) { 1756static void updateValueInputWidth_(iWidget *dlg) {
1757 const iRect safeRoot = safeRect_Root(get_Root()); 1757 const iRect safeRoot = safeRect_Root(dlg->root);
1758 const iInt2 rootSize = safeRoot.size; 1758 const iInt2 rootSize = safeRoot.size;
1759 iWidget * title = findChild_Widget(dlg, "valueinput.title"); 1759 iWidget * title = findChild_Widget(dlg, "valueinput.title");
1760 iWidget * prompt = findChild_Widget(dlg, "valueinput.prompt"); 1760 iWidget * prompt = findChild_Widget(dlg, "valueinput.prompt");
@@ -1966,7 +1966,7 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg,
1966 } 1966 }
1967 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); 1967 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
1968 addChild_Widget(dlg, iClob(makeDialogButtons_Widget(items, numItems))); 1968 addChild_Widget(dlg, iClob(makeDialogButtons_Widget(items, numItems)));
1969 addChild_Widget(get_Window()->root.widget, iClob(dlg)); 1969 addChild_Widget(dlg->root->widget, iClob(dlg));
1970 arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't 1970 arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't
1971 be arranged correctly unless it's here. */ 1971 be arranged correctly unless it's here. */
1972 finalizeSheet_Widget(dlg); 1972 finalizeSheet_Widget(dlg);
@@ -2412,7 +2412,7 @@ iWidget *makePreferences_Widget(void) {
2412 addChild_Widget(dlg, 2412 addChild_Widget(dlg,
2413 iClob(makeDialogButtons_Widget( 2413 iClob(makeDialogButtons_Widget(
2414 (iMenuItem[]){ { "${dismiss}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1))); 2414 (iMenuItem[]){ { "${dismiss}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1)));
2415 addChild_Widget(get_Window()->root.widget, iClob(dlg)); 2415 addChild_Widget(dlg->root->widget, iClob(dlg));
2416 finalizeSheet_Widget(dlg); 2416 finalizeSheet_Widget(dlg);
2417 //printTree_Widget(dlg); 2417 //printTree_Widget(dlg);
2418 return dlg; 2418 return dlg;
@@ -2445,7 +2445,7 @@ iWidget *makeBookmarkEditor_Widget(void) {
2445 KMOD_PRIMARY, 2445 KMOD_PRIMARY,
2446 "bmed.accept" } }, 2446 "bmed.accept" } },
2447 2))); 2447 2)));
2448 addChild_Widget(get_Window()->root.widget, iClob(dlg)); 2448 addChild_Widget(get_Root()->widget, iClob(dlg));
2449 finalizeSheet_Widget(dlg); 2449 finalizeSheet_Widget(dlg);
2450 return dlg; 2450 return dlg;
2451} 2451}
@@ -2578,7 +2578,7 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) {
2578 setId_Widget(child_Widget(buttons, childCount_Widget(buttons) - 1), "feedcfg.save"); 2578 setId_Widget(child_Widget(buttons, childCount_Widget(buttons) - 1), "feedcfg.save");
2579 arrange_Widget(dlg); 2579 arrange_Widget(dlg);
2580 as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; 2580 as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x;
2581 addChild_Widget(get_Window()->root.widget, iClob(dlg)); 2581 addChild_Widget(get_Root()->widget, iClob(dlg));
2582 finalizeSheet_Widget(dlg); 2582 finalizeSheet_Widget(dlg);
2583 /* Initialize. */ { 2583 /* Initialize. */ {
2584 const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; 2584 const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL;
@@ -2654,7 +2654,7 @@ iWidget *makeIdentityCreation_Widget(void) {
2654 KMOD_PRIMARY, 2654 KMOD_PRIMARY,
2655 "ident.accept" } }, 2655 "ident.accept" } },
2656 2))); 2656 2)));
2657 addChild_Widget(get_Window()->root.widget, iClob(dlg)); 2657 addChild_Widget(get_Root()->widget, iClob(dlg));
2658 finalizeSheet_Widget(dlg); 2658 finalizeSheet_Widget(dlg);
2659 return dlg; 2659 return dlg;
2660} 2660}
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 2d9bf45d..5d924b57 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -50,6 +50,7 @@ iDefineObjectConstruction(Widget)
50 50
51void init_Widget(iWidget *d) { 51void init_Widget(iWidget *d) {
52 init_String(&d->id); 52 init_String(&d->id);
53 d->root = get_Root(); /* never changes after this */
53 d->flags = 0; 54 d->flags = 0;
54 d->rect = zero_Rect(); 55 d->rect = zero_Rect();
55 d->minSize = zero_I2(); 56 d->minSize = zero_I2();
@@ -81,7 +82,7 @@ void deinit_Widget(iWidget *d) {
81//#endif 82//#endif
82 deinit_String(&d->id); 83 deinit_String(&d->id);
83 if (d->flags & keepOnTop_WidgetFlag) { 84 if (d->flags & keepOnTop_WidgetFlag) {
84 removeAll_PtrArray(onTop_Root(get_Root()), d); 85 removeAll_PtrArray(onTop_Root(d->root), d);
85 } 86 }
86 if (d->flags & visualOffset_WidgetFlag) { 87 if (d->flags & visualOffset_WidgetFlag) {
87 removeTicker_App(visualOffsetAnimation_Widget_, d); 88 removeTicker_App(visualOffsetAnimation_Widget_, d);
@@ -95,10 +96,10 @@ static void aboutToBeDestroyed_Widget_(iWidget *d) {
95 return; 96 return;
96 } 97 }
97 if (flags_Widget(d) & keepOnTop_WidgetFlag) { 98 if (flags_Widget(d) & keepOnTop_WidgetFlag) {
98 removeOne_PtrArray(onTop_Root(get_Root()), d); 99 removeOne_PtrArray(onTop_Root(d->root), d);
99 } 100 }
100 if (isHover_Widget(d)) { 101 if (isHover_Widget(d)) {
101 get_Root()->hover = NULL; 102 d->root->hover = NULL;
102 } 103 }
103 iForEach(ObjectList, i, d->children) { 104 iForEach(ObjectList, i, d->children) {
104 aboutToBeDestroyed_Widget_(as_Widget(i.object)); 105 aboutToBeDestroyed_Widget_(as_Widget(i.object));
@@ -111,11 +112,10 @@ void destroy_Widget(iWidget *d) {
111 postRefresh_App(); 112 postRefresh_App();
112 } 113 }
113 aboutToBeDestroyed_Widget_(d); 114 aboutToBeDestroyed_Widget_(d);
114 iRoot *root = get_Root(); 115 if (!d->root->pendingDestruction) {
115 if (!root->pendingDestruction) { 116 d->root->pendingDestruction = new_PtrSet();
116 root->pendingDestruction = new_PtrSet();
117 } 117 }
118 insert_PtrSet(root->pendingDestruction, d); 118 insert_PtrSet(d->root->pendingDestruction, d);
119 } 119 }
120} 120}
121 121
@@ -139,7 +139,7 @@ void setFlags_Widget(iWidget *d, int64_t flags, iBool set) {
139 } 139 }
140 iChangeFlags(d->flags, flags, set); 140 iChangeFlags(d->flags, flags, set);
141 if (flags & keepOnTop_WidgetFlag) { 141 if (flags & keepOnTop_WidgetFlag) {
142 iPtrArray *onTop = onTop_Root(get_Root()); 142 iPtrArray *onTop = onTop_Root(d->root);
143 if (set) { 143 if (set) {
144 iAssert(indexOf_PtrArray(onTop, d) == iInvalidPos); 144 iAssert(indexOf_PtrArray(onTop, d) == iInvalidPos);
145 pushBack_PtrArray(onTop, d); 145 pushBack_PtrArray(onTop, d);
@@ -182,12 +182,16 @@ void setPadding_Widget(iWidget *d, int left, int top, int right, int bottom) {
182 d->padding[3] = bottom; 182 d->padding[3] = bottom;
183} 183}
184 184
185iWidget *root_Widget(const iWidget *d) {
186 return d ? d->root->widget : NULL;
187}
188
185void showCollapsed_Widget(iWidget *d, iBool show) { 189void showCollapsed_Widget(iWidget *d, iBool show) {
186 const iBool isVisible = !(d->flags & hidden_WidgetFlag); 190 const iBool isVisible = !(d->flags & hidden_WidgetFlag);
187 if ((isVisible && !show) || (!isVisible && show)) { 191 if ((isVisible && !show) || (!isVisible && show)) {
188 setFlags_Widget(d, hidden_WidgetFlag, !show); 192 setFlags_Widget(d, hidden_WidgetFlag, !show);
189 /* The entire UI may be affected, if parents are resized due to the (un)collapsing. */ 193 /* The entire UI may be affected, if parents are resized due to the (un)collapsing. */
190 arrange_Widget(get_Window()->root.widget); 194 arrange_Widget(root_Widget(d));
191 postRefresh_App(); 195 postRefresh_App();
192 } 196 }
193} 197}
@@ -352,7 +356,7 @@ static size_t numArrangedChildren_Widget_(const iWidget *d) {
352 356
353static void centerHorizontal_Widget_(iWidget *d) { 357static void centerHorizontal_Widget_(iWidget *d) {
354 d->rect.pos.x = ((d->parent ? width_Rect(innerRect_Widget_(d->parent)) 358 d->rect.pos.x = ((d->parent ? width_Rect(innerRect_Widget_(d->parent))
355 : size_Root(get_Root()).x) - 359 : size_Root(d->root).x) -
356 width_Rect(d->rect)) / 360 width_Rect(d->rect)) /
357 2; 361 2;
358 TRACE(d, "center horizontally: %d", d->rect.pos.x); 362 TRACE(d, "center horizontally: %d", d->rect.pos.x);
@@ -706,7 +710,7 @@ iBool containsExpanded_Widget(const iWidget *d, iInt2 coord, int expand) {
706 const iRect bounds = { 710 const iRect bounds = {
707 zero_I2(), 711 zero_I2(),
708 addY_I2(d->rect.size, 712 addY_I2(d->rect.size,
709 d->flags & drawBackgroundToBottom_WidgetFlag ? size_Root(get_Root()).y : 0) 713 d->flags & drawBackgroundToBottom_WidgetFlag ? size_Root(d->root).y : 0)
710 }; 714 };
711 return contains_Rect(expand ? expanded_Rect(bounds, init1_I2(expand)) : bounds, 715 return contains_Rect(expand ? expanded_Rect(bounds, init1_I2(expand)) : bounds,
712 localCoord_Widget(d, coord)); 716 localCoord_Widget(d, coord));
@@ -734,24 +738,25 @@ static iBool filterEvent_Widget_(const iWidget *d, const SDL_Event *ev) {
734} 738}
735 739
736void unhover_Widget(void) { 740void unhover_Widget(void) {
741 /* TODO: Which root? */
737 get_Root()->hover = NULL; 742 get_Root()->hover = NULL;
738} 743}
739 744
740iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { 745iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
746 iAssert(d->root == get_Root());
741 if (!d->parent) { 747 if (!d->parent) {
742 //setCurrent_Root(d);
743 if (ev->type == SDL_MOUSEMOTION) { 748 if (ev->type == SDL_MOUSEMOTION) {
744 /* Hover widget may change. */ 749 /* Hover widget may change. */
745 setHover_Widget(NULL); 750 setHover_Widget(NULL);
746 } 751 }
747 if (get_Root()->focus && isKeyboardEvent_(ev)) { 752 if (d->root->focus && isKeyboardEvent_(ev)) {
748 /* Root dispatches keyboard events directly to the focused widget. */ 753 /* Root dispatches keyboard events directly to the focused widget. */
749 if (dispatchEvent_Widget(get_Root()->focus, ev)) { 754 if (dispatchEvent_Widget(d->root->focus, ev)) {
750 return iTrue; 755 return iTrue;
751 } 756 }
752 } 757 }
753 /* Root offers events first to widgets on top. */ 758 /* Root offers events first to widgets on top. */
754 iReverseForEach(PtrArray, i, get_Root()->onTop) { 759 iReverseForEach(PtrArray, i, d->root->onTop) {
755 iWidget *widget = *i.value; 760 iWidget *widget = *i.value;
756 if (isVisible_Widget(widget) && dispatchEvent_Widget(widget, ev)) { 761 if (isVisible_Widget(widget) && dispatchEvent_Widget(widget, ev)) {
757#if 0 762#if 0
@@ -775,7 +780,7 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
775 } 780 }
776 } 781 }
777 else if (ev->type == SDL_MOUSEMOTION && 782 else if (ev->type == SDL_MOUSEMOTION &&
778 (!get_Root()->hover || hasParent_Widget(d, get_Root()->hover)) && 783 (!d->root->hover || hasParent_Widget(d, d->root->hover)) &&
779 flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag && 784 flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag &&
780 ~flags_Widget(d) & disabled_WidgetFlag) { 785 ~flags_Widget(d) & disabled_WidgetFlag) {
781 if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) { 786 if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) {
@@ -793,7 +798,7 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
793 handle the events first. */ 798 handle the events first. */
794 iReverseForEach(ObjectList, i, d->children) { 799 iReverseForEach(ObjectList, i, d->children) {
795 iWidget *child = as_Widget(i.object); 800 iWidget *child = as_Widget(i.object);
796 if (child == get_Root()->focus && isKeyboardEvent_(ev)) { 801 if (child == d->root->focus && isKeyboardEvent_(ev)) {
797 continue; /* Already dispatched. */ 802 continue; /* Already dispatched. */
798 } 803 }
799 if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) { 804 if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) {
@@ -845,8 +850,8 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
845 850
846static iBool scrollOverflow_Widget_(iWidget *d, int delta) { 851static iBool scrollOverflow_Widget_(iWidget *d, int delta) {
847 iRect bounds = bounds_Widget(d); 852 iRect bounds = bounds_Widget(d);
848 const iInt2 rootSize = size_Root(get_Root()); 853 const iInt2 rootSize = size_Root(d->root);
849 const iRect winRect = safeRect_Root(get_Root()); 854 const iRect winRect = safeRect_Root(d->root);
850 const int yTop = top_Rect(winRect); 855 const int yTop = top_Rect(winRect);
851 const int yBottom = bottom_Rect(winRect); 856 const int yBottom = bottom_Rect(winRect);
852 //const int safeBottom = rootSize.y - yBottom; 857 //const int safeBottom = rootSize.y - yBottom;
@@ -979,14 +984,14 @@ void drawBackground_Widget(const iWidget *d) {
979 break; 984 break;
980 } 985 }
981 fillRect_Paint(&p, 986 fillRect_Paint(&p,
982 rect_Root(get_Root()), 987 rect_Root(d->root),
983 fadeColor); 988 fadeColor);
984 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); 989 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
985 } 990 }
986 if (d->bgColor >= 0 || d->frameColor >= 0) { 991 if (d->bgColor >= 0 || d->frameColor >= 0) {
987 iRect rect = bounds_Widget(d); 992 iRect rect = bounds_Widget(d);
988 if (d->flags & drawBackgroundToBottom_WidgetFlag) { 993 if (d->flags & drawBackgroundToBottom_WidgetFlag) {
989 rect.size.y = size_Root(get_Root()).y - top_Rect(rect); 994 rect.size.y = size_Root(d->root).y - top_Rect(rect);
990 } 995 }
991 iPaint p; 996 iPaint p;
992 init_Paint(&p); 997 init_Paint(&p);
@@ -994,7 +999,7 @@ void drawBackground_Widget(const iWidget *d) {
994#if defined (iPlatformAppleMobile) 999#if defined (iPlatformAppleMobile)
995 if (d->flags & (drawBackgroundToHorizontalSafeArea_WidgetFlag | 1000 if (d->flags & (drawBackgroundToHorizontalSafeArea_WidgetFlag |
996 drawBackgroundToVerticalSafeArea_WidgetFlag)) { 1001 drawBackgroundToVerticalSafeArea_WidgetFlag)) {
997 const iInt2 rootSize = size_Root(get_Root()); 1002 const iInt2 rootSize = size_Root(d->root);
998 const iInt2 center = divi_I2(rootSize, 2); 1003 const iInt2 center = divi_I2(rootSize, 2);
999 int top = 0, right = 0, bottom = 0, left = 0; 1004 int top = 0, right = 0, bottom = 0, left = 0;
1000 if (d->flags & drawBackgroundToHorizontalSafeArea_WidgetFlag) { 1005 if (d->flags & drawBackgroundToHorizontalSafeArea_WidgetFlag) {
@@ -1058,7 +1063,7 @@ void drawChildren_Widget(const iWidget *d) {
1058 } 1063 }
1059 /* Root draws the on-top widgets on top of everything else. */ 1064 /* Root draws the on-top widgets on top of everything else. */
1060 if (!d->parent) { 1065 if (!d->parent) {
1061 iConstForEach(PtrArray, i, onTop_Root(get_Root())) { 1066 iConstForEach(PtrArray, i, onTop_Root(d->root)) {
1062 const iWidget *top = *i.value; 1067 const iWidget *top = *i.value;
1063 draw_Widget(top); 1068 draw_Widget(top);
1064 } 1069 }
@@ -1175,7 +1180,7 @@ iAny *hitChild_Widget(const iWidget *d, iInt2 coord) {
1175 } 1180 }
1176 /* Check for on-top widgets first. */ 1181 /* Check for on-top widgets first. */
1177 if (!d->parent) { 1182 if (!d->parent) {
1178 iReverseForEach(PtrArray, i, onTop_Root(get_Root())) { 1183 iReverseForEach(PtrArray, i, onTop_Root(d->root)) {
1179 iWidget *child = i.ptr; 1184 iWidget *child = i.ptr;
1180// printf("ontop: %s (%s) hidden:%d hittable:%d\n", cstr_String(id_Widget(child)), 1185// printf("ontop: %s (%s) hidden:%d hittable:%d\n", cstr_String(id_Widget(child)),
1181// class_Widget(child)->name, 1186// class_Widget(child)->name,
@@ -1263,12 +1268,12 @@ iBool isDisabled_Widget(const iAnyObject *d) {
1263 1268
1264iBool isFocused_Widget(const iAnyObject *d) { 1269iBool isFocused_Widget(const iAnyObject *d) {
1265 iAssert(isInstance_Object(d, &Class_Widget)); 1270 iAssert(isInstance_Object(d, &Class_Widget));
1266 return get_Root()->focus == d; 1271 return ((const iWidget *) d)->root->focus == d;
1267} 1272}
1268 1273
1269iBool isHover_Widget(const iAnyObject *d) { 1274iBool isHover_Widget(const iAnyObject *d) {
1270 iAssert(isInstance_Object(d, &Class_Widget)); 1275 iAssert(isInstance_Object(d, &Class_Widget));
1271 return get_Root()->hover == d; 1276 return ((const iWidget *) d)->root->hover == d;
1272} 1277}
1273 1278
1274iBool isSelected_Widget(const iAnyObject *d) { 1279iBool isSelected_Widget(const iAnyObject *d) {
@@ -1314,13 +1319,15 @@ iBool isAffectedByVisualOffset_Widget(const iWidget *d) {
1314} 1319}
1315 1320
1316void setFocus_Widget(iWidget *d) { 1321void setFocus_Widget(iWidget *d) {
1317 if (get_Root()->focus != d) { 1322 iRoot *root = d ? d->root : get_Root();
1318 if (get_Root()->focus) { 1323 if (root->focus != d) {
1319 iAssert(!contains_PtrSet(get_Root()->pendingDestruction, get_Root()->focus)); 1324 if (root->focus) {
1320 postCommand_Widget(get_Root()->focus, "focus.lost"); 1325 iAssert(!contains_PtrSet(root->pendingDestruction, root->focus));
1326 postCommand_Widget(root->focus, "focus.lost");
1321 } 1327 }
1322 get_Root()->focus = d; 1328 root->focus = d;
1323 if (d) { 1329 if (d) {
1330 iAssert(root == d->root);
1324 iAssert(flags_Widget(d) & focusable_WidgetFlag); 1331 iAssert(flags_Widget(d) & focusable_WidgetFlag);
1325 postCommand_Widget(d, "focus.gained"); 1332 postCommand_Widget(d, "focus.gained");
1326 } 1333 }
@@ -1332,7 +1339,12 @@ iWidget *focus_Widget(void) {
1332} 1339}
1333 1340
1334void setHover_Widget(iWidget *d) { 1341void setHover_Widget(iWidget *d) {
1335 get_Root()->hover = d; 1342 if (d) {
1343 d->root->hover = d;
1344 }
1345 else {
1346 get_Root()->hover = NULL;
1347 }
1336} 1348}
1337 1349
1338iWidget *hover_Widget(void) { 1350iWidget *hover_Widget(void) {
@@ -1380,7 +1392,7 @@ static const iWidget *findFocusRoot_Widget_(const iWidget *d) {
1380} 1392}
1381 1393
1382iAny *findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusDir) { 1394iAny *findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusDir) {
1383 const iWidget *root = findFocusRoot_Widget_(get_Window()->root.widget); 1395 const iWidget *root = findFocusRoot_Widget_(get_Root()->widget);
1384 iAssert(root != NULL); 1396 iAssert(root != NULL);
1385 iBool getNext = (startFrom ? iFalse : iTrue); 1397 iBool getNext = (startFrom ? iFalse : iTrue);
1386 const iWidget *found = findFocusable_Widget_(root, startFrom, &getNext, focusDir); 1398 const iWidget *found = findFocusable_Widget_(root, startFrom, &getNext, focusDir);
@@ -1392,14 +1404,21 @@ iAny *findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusD
1392} 1404}
1393 1405
1394void setMouseGrab_Widget(iWidget *d) { 1406void setMouseGrab_Widget(iWidget *d) {
1395 if (get_Root()->mouseGrab != d) { 1407 iRoot *root = d ? d->root : get_Root();
1396 get_Root()->mouseGrab = d; 1408 if (root->mouseGrab != d) {
1409 root->mouseGrab = d;
1397 SDL_CaptureMouse(d != NULL); 1410 SDL_CaptureMouse(d != NULL);
1398 } 1411 }
1399} 1412}
1400 1413
1401iWidget *mouseGrab_Widget(void) { 1414iWidget *mouseGrab_Widget(void) {
1402 return get_Root()->mouseGrab; 1415 iWindow *win = get_Window();
1416 iForIndices(i, win->roots) {
1417 if (win->roots[i] && win->roots[i]->mouseGrab) {
1418 return win->roots[i]->mouseGrab;
1419 }
1420 }
1421 return NULL;
1403} 1422}
1404 1423
1405void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) { 1424void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) {
@@ -1433,7 +1452,7 @@ void refresh_Widget(const iAnyObject *d) {
1433} 1452}
1434 1453
1435void raise_Widget(iWidget *d) { 1454void raise_Widget(iWidget *d) {
1436 iPtrArray *onTop = onTop_Root(get_Root()); 1455 iPtrArray *onTop = onTop_Root(d->root);
1437 if (d->flags & keepOnTop_WidgetFlag) { 1456 if (d->flags & keepOnTop_WidgetFlag) {
1438 iAssert(indexOf_PtrArray(onTop, d) != iInvalidPos); 1457 iAssert(indexOf_PtrArray(onTop, d) != iInvalidPos);
1439 removeOne_PtrArray(onTop, d); 1458 removeOne_PtrArray(onTop, d);
diff --git a/src/ui/widget.h b/src/ui/widget.h
index ab08a9b2..f126d40d 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -34,6 +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 */
38
37#define iDeclareWidgetClass(className) \ 39#define iDeclareWidgetClass(className) \
38 iDeclareType(className); \ 40 iDeclareType(className); \
39 typedef iWidgetClass i##className##Class; \ 41 typedef iWidgetClass i##className##Class; \
@@ -138,6 +140,7 @@ struct Impl_Widget {
138 iObjectList *children; 140 iObjectList *children;
139 iWidget * parent; 141 iWidget * parent;
140 iBool (*commandHandler)(iWidget *, const char *); 142 iBool (*commandHandler)(iWidget *, const char *);
143 iRoot * root;
141}; 144};
142 145
143iDeclareObjectConstruction(Widget) 146iDeclareObjectConstruction(Widget)
@@ -166,7 +169,8 @@ void destroy_Widget (iWidget *); /* widget removed and deleted later
166void destroyPending_Widget (void); 169void destroyPending_Widget (void);
167void releaseChildren_Widget (iWidget *); 170void releaseChildren_Widget (iWidget *);
168 171
169const iString *id_Widget (const iWidget *); 172iWidget * root_Widget (const iWidget *);
173const iString * id_Widget (const iWidget *);
170int64_t flags_Widget (const iWidget *); 174int64_t flags_Widget (const iWidget *);
171iRect bounds_Widget (const iWidget *); /* outer bounds */ 175iRect bounds_Widget (const iWidget *); /* outer bounds */
172iRect innerBounds_Widget (const iWidget *); 176iRect innerBounds_Widget (const iWidget *);
diff --git a/src/ui/window.c b/src/ui/window.c
index 5c10fea6..e47b579f 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -161,13 +161,40 @@ static void removeMacMenus_(void) {
161} 161}
162#endif 162#endif
163 163
164static int numRoots_Window(const iWindow *d) {
165 int num = 0;
166 iForIndices(i, d->roots) {
167 if (d->roots[i]) num++;
168 }
169 return num;
170}
171
164static void setupUserInterface_Window(iWindow *d) { 172static void setupUserInterface_Window(iWindow *d) {
165#if defined (iPlatformAppleDesktop) 173#if defined (iPlatformAppleDesktop)
166 insertMacMenus_(); 174 insertMacMenus_();
167#endif 175#endif
168 setCurrent_Root(&d->root); 176 iForIndices(i, d->roots) {
169 createUserInterface_Root(&d->root); 177 d->roots[i] = new_Root();
170 setCurrent_Root(NULL); 178 setCurrent_Root(d->roots[i]);
179 createUserInterface_Root(d->roots[i]);
180 setCurrent_Root(NULL);
181 }
182}
183
184static void windowSizeChanged_Window_(iWindow *d) {
185 const int numRoots = numRoots_Window(d);
186 /* Horizontal split frame. */
187 const iInt2 rootSize = div_I2(d->size, init_I2(numRoots, 1));
188 iForIndices(i, d->roots) {
189 iRoot *root = d->roots[i];
190 if (root) {
191 root->widget->rect.pos = init_I2(rootSize.x * i, 0);
192 root->widget->rect.size = rootSize; /* Reposition roots. */
193 root->widget->minSize = rootSize;
194 updatePadding_Root(root);
195 arrange_Widget(root->widget);
196 }
197 }
171} 198}
172 199
173static void updateSize_Window_(iWindow *d, iBool notifyAlways) { 200static void updateSize_Window_(iWindow *d, iBool notifyAlways) {
@@ -175,13 +202,10 @@ static void updateSize_Window_(iWindow *d, iBool notifyAlways) {
175 const iInt2 oldSize = *size; 202 const iInt2 oldSize = *size;
176 SDL_GetRendererOutputSize(d->render, &size->x, &size->y); 203 SDL_GetRendererOutputSize(d->render, &size->x, &size->y);
177 size->y -= d->keyboardHeight; 204 size->y -= d->keyboardHeight;
178 d->root.widget->rect.size = *size; /* Reposition roots. */
179 d->root.widget->minSize = *size;
180 if (notifyAlways || !isEqual_I2(oldSize, *size)) { 205 if (notifyAlways || !isEqual_I2(oldSize, *size)) {
181 updatePadding_Root(&d->root); 206 windowSizeChanged_Window_(d);
182 const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x); 207 const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x);
183 const iBool isVert = (d->place.lastNotifiedSize.y != size->y); 208 const iBool isVert = (d->place.lastNotifiedSize.y != size->y);
184 arrange_Widget(d->root.widget);
185 postCommandf_App("window.resized width:%d height:%d horiz:%d vert:%d", 209 postCommandf_App("window.resized width:%d height:%d horiz:%d vert:%d",
186 size->x, 210 size->x,
187 size->y, 211 size->y,
@@ -196,8 +220,8 @@ static void updateSize_Window_(iWindow *d, iBool notifyAlways) {
196void drawWhileResizing_Window(iWindow *d, int w, int h) { 220void drawWhileResizing_Window(iWindow *d, int w, int h) {
197 /* This is called while a window resize is in progress, so we can be pretty confident 221 /* This is called while a window resize is in progress, so we can be pretty confident
198 the size has actually changed. */ 222 the size has actually changed. */
199 d->root.widget->rect.size = coord_Window(d, w, h); 223 d->size = init_I2(w, h);
200 arrange_Widget(d->root.widget); 224 windowSizeChanged_Window_(d);
201 draw_Window(d); 225 draw_Window(d);
202} 226}
203 227
@@ -348,7 +372,7 @@ void init_Window(iWindow *d, iRect rect) {
348 theWindow_ = d; 372 theWindow_ = d;
349 d->win = NULL; 373 d->win = NULL;
350 d->size = zero_I2(); /* will be updated below */ 374 d->size = zero_I2(); /* will be updated below */
351 init_Root(&d->root); 375 iZap(d->roots);
352 iZap(d->cursors); 376 iZap(d->cursors);
353 d->place.initialPos = rect.pos; 377 d->place.initialPos = rect.pos;
354 d->place.normalRect = rect; 378 d->place.normalRect = rect;
@@ -452,13 +476,17 @@ void init_Window(iWindow *d, iRect rect) {
452} 476}
453 477
454void deinit_Window(iWindow *d) { 478void deinit_Window(iWindow *d) {
455 setCurrent_Root(&d->root);
456 iRecycle(); 479 iRecycle();
457 if (theWindow_ == d) { 480 if (theWindow_ == d) {
458 theWindow_ = NULL; 481 theWindow_ = NULL;
459 } 482 }
460 deinit_Root(&d->root); 483 iForIndices(i, d->roots) {
461 setCurrent_Root(NULL); 484 if (d->roots[i]) {
485 setCurrent_Root(d->roots[i]);
486 deinit_Root(d->roots[i]);
487 }
488 }
489 setCurrent_Root(NULL);
462 deinit_Text(); 490 deinit_Text();
463 SDL_DestroyRenderer(d->render); 491 SDL_DestroyRenderer(d->render);
464 SDL_DestroyWindow(d->win); 492 SDL_DestroyWindow(d->win);
@@ -483,6 +511,19 @@ iBool isFullscreen_Window(const iWindow *d) {
483 return snap_Window(d) == fullscreen_WindowSnap; 511 return snap_Window(d) == fullscreen_WindowSnap;
484} 512}
485 513
514iRoot *findRoot_Window(const iWindow *d, const iWidget *widget) {
515 while (widget->parent) {
516 widget = widget->parent;
517 }
518 iForIndices(i, d->roots) {
519 if (d->roots[i] && d->roots[i]->widget == widget) {
520 return d->roots[i];
521 }
522 }
523 iAssert(iFalse); /* it must be under some Root */
524 return NULL;
525}
526
486static void invalidate_Window_(iWindow *d) { 527static void invalidate_Window_(iWindow *d) {
487 iUnused(d); 528 iUnused(d);
488 resetFonts_Text(); 529 resetFonts_Text();
@@ -639,7 +680,6 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
639 return iTrue; 680 return iTrue;
640 } 681 }
641 case SDL_WINDOWEVENT_RESIZED: 682 case SDL_WINDOWEVENT_RESIZED:
642 updatePadding_Root(&d->root);
643 if (d->isMinimized) { 683 if (d->isMinimized) {
644 updateSize_Window_(d, iTrue); 684 updateSize_Window_(d, iTrue);
645 return iTrue; 685 return iTrue;
@@ -771,17 +811,25 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
771 event.button.x = pos.x; 811 event.button.x = pos.x;
772 event.button.y = pos.y; 812 event.button.y = pos.y;
773 } 813 }
774 setCurrent_Root(&d->root); 814 const iWidget *oldHovers[2] = {
775 iWidget *widget = d->root.widget; 815 d->roots[0]->hover,
816 d->roots[1]->hover,
817 };
818 iBool wasUsed = iFalse;
819 /* Dispatch first to the mouse-grabbed widget. */
820// iWidget *widget = d->root.widget;
776 if (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEWHEEL || 821 if (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEWHEEL ||
777 event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) { 822 event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {
778 if (mouseGrab_Widget()) { 823 if (mouseGrab_Widget()) {
779 widget = mouseGrab_Widget(); 824 iWidget *grabbed = mouseGrab_Widget();
825 setCurrent_Root(findRoot_Window(d, grabbed));
826 wasUsed = dispatchEvent_Widget(grabbed, &event);
780 } 827 }
781 } 828 }
782 iWidget *oldHover = hover_Widget();
783 /* Dispatch the event to the tree of widgets. */ 829 /* Dispatch the event to the tree of widgets. */
784 iBool wasUsed = dispatchEvent_Widget(widget, &event); 830 if (!wasUsed) {
831 wasUsed = dispatchEvent_Window(d, &event);
832 }
785 if (!wasUsed) { 833 if (!wasUsed) {
786 /* As a special case, clicking the middle mouse button can be used for pasting 834 /* As a special case, clicking the middle mouse button can be used for pasting
787 from the clipboard. */ 835 from the clipboard. */
@@ -793,14 +841,16 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
793 paste.key.keysym.mod = KMOD_PRIMARY; 841 paste.key.keysym.mod = KMOD_PRIMARY;
794 paste.key.state = SDL_PRESSED; 842 paste.key.state = SDL_PRESSED;
795 paste.key.timestamp = SDL_GetTicks(); 843 paste.key.timestamp = SDL_GetTicks();
796 wasUsed = dispatchEvent_Widget(widget, &paste); 844 wasUsed = dispatchEvent_Window(d, &paste);
797 } 845 }
798 if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) { 846 if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) {
799 postContextClick_Window(d, &event.button); 847 postContextClick_Window(d, &event.button);
800 } 848 }
801 } 849 }
802 if (isMetricsChange_UserEvent(&event)) { 850 if (isMetricsChange_UserEvent(&event)) {
803 updateMetrics_Root(&d->root); 851 iForIndices(i, d->roots) {
852 updateMetrics_Root(d->roots[i]);
853 }
804 } 854 }
805 if (isCommand_UserEvent(&event, "lang.changed")) { 855 if (isCommand_UserEvent(&event, "lang.changed")) {
806#if defined (iPlatformAppleDesktop) 856#if defined (iPlatformAppleDesktop)
@@ -809,11 +859,14 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
809 insertMacMenus_(); 859 insertMacMenus_();
810#endif 860#endif
811 invalidate_Window_(d); 861 invalidate_Window_(d);
812 updatePreferencesLayout_Widget(findChild_Widget(d->root.widget, "prefs")); 862 iForIndices(i, d->roots) {
813 arrange_Widget(d->root.widget); 863 if (d->roots[i]) {
814 //printTree_Widget(findChild_Widget(d->root, "prefs")); 864 updatePreferencesLayout_Widget(findChild_Widget(d->roots[i]->widget, "prefs"));
865 arrange_Widget(d->roots[i]->widget);
866 }
867 }
815 } 868 }
816 if (oldHover != hover_Widget()) { 869 if (oldHovers[0] != d->roots[0]->hover || oldHovers[1] != d->roots[1]->hover) {
817 postRefresh_App(); 870 postRefresh_App();
818 } 871 }
819 if (event.type == SDL_MOUSEMOTION) { 872 if (event.type == SDL_MOUSEMOTION) {
@@ -825,9 +878,35 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
825 return iFalse; 878 return iFalse;
826} 879}
827 880
881iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) {
882 /* TODO: Dispatch to input-focused root first. */
883 iForIndices(i, d->roots) {
884 if (d->roots[i]) {
885 setCurrent_Root(d->roots[i]);
886 const iBool wasUsed = dispatchEvent_Widget(d->roots[i]->widget, ev);
887 if (wasUsed) {
888 return iTrue;
889 }
890 }
891 }
892 return iFalse;
893}
894
895iAnyObject *hitChild_Window(const iWindow *d, iInt2 coord) {
896 iForIndices(i, d->roots) {
897 if (d->roots[i]) {
898 iAnyObject *hit = hitChild_Widget(d->roots[i]->widget, coord);
899 if (hit) {
900 return hit;
901 }
902 }
903 }
904 return NULL;
905}
906
828iBool postContextClick_Window(iWindow *d, const SDL_MouseButtonEvent *ev) { 907iBool postContextClick_Window(iWindow *d, const SDL_MouseButtonEvent *ev) {
829 /* A context menu may still get triggered here. */ 908 /* A context menu may still get triggered here. */
830 const iWidget *hit = hitChild_Widget(d->root.widget, init_I2(ev->x, ev->y)); 909 const iWidget *hit = hitChild_Window(d, init_I2(ev->x, ev->y));
831 while (hit && isEmpty_String(id_Widget(hit))) { 910 while (hit && isEmpty_String(id_Widget(hit))) {
832 hit = parent_Widget(hit); 911 hit = parent_Widget(hit);
833 } 912 }
@@ -872,31 +951,36 @@ void draw_Window(iWindow *d) {
872 /* Draw widgets. */ 951 /* Draw widgets. */
873 d->frameTime = SDL_GetTicks(); 952 d->frameTime = SDL_GetTicks();
874 if (isExposed_Window(d)) { 953 if (isExposed_Window(d)) {
875 setCurrent_Root(&d->root); 954 iForIndices(i, d->roots) {
876 draw_Widget(d->root.widget); 955 iRoot *root = d->roots[i];
956 if (root) {
957 setCurrent_Root(root);
958 draw_Widget(root->widget);
877#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 959#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
878 /* App icon. */ 960 /* App icon. */
879 const iWidget *appIcon = findChild_Widget(d->root.widget, "winbar.icon"); 961 const iWidget *appIcon = findChild_Widget(root->widget, "winbar.icon");
880 if (isVisible_Widget(appIcon)) { 962 if (isVisible_Widget(appIcon)) {
881 const int size = appIconSize_(); 963 const int size = appIconSize_();
882 const iRect rect = bounds_Widget(appIcon); 964 const iRect rect = bounds_Widget(appIcon);
883 const iInt2 mid = mid_Rect(rect); 965 const iInt2 mid = mid_Rect(rect);
884 const iBool isLight = isLight_ColorTheme(colorTheme_App()); 966 const iBool isLight = isLight_ColorTheme(colorTheme_App());
885 iColor iconColor = get_Color(gotFocus || isLight ? white_ColorId : uiAnnotation_ColorId); 967 iColor iconColor = get_Color(gotFocus || isLight ? white_ColorId : uiAnnotation_ColorId);
886 SDL_SetTextureColorMod(d->appIcon, iconColor.r, iconColor.g, iconColor.b); 968 SDL_SetTextureColorMod(d->appIcon, iconColor.r, iconColor.g, iconColor.b);
887 SDL_SetTextureAlphaMod(d->appIcon, gotFocus || !isLight ? 255 : 92); 969 SDL_SetTextureAlphaMod(d->appIcon, gotFocus || !isLight ? 255 : 92);
888 SDL_RenderCopy( 970 SDL_RenderCopy(
889 d->render, 971 d->render,
890 d->appIcon, 972 d->appIcon,
891 NULL, 973 NULL,
892 &(SDL_Rect){ left_Rect(rect) + gap_UI * 1.25f, mid.y - size / 2, size, size }); 974 &(SDL_Rect){ left_Rect(rect) + gap_UI * 1.25f, mid.y - size / 2, size, size });
975 }
976#endif
977 }
893 } 978 }
894#endif
895 setCurrent_Root(NULL); 979 setCurrent_Root(NULL);
896 } 980 }
897#if 0 981#if 0
898 /* Text cache debugging. */ { 982 /* Text cache debugging. */ {
899 SDL_Rect rect = { d->root.widget->rect.size.x - 640, 0, 640, 2.5 * 640 }; 983 SDL_Rect rect = { d->roots[0]->widget->rect.size.x - 640, 0, 640, 2.5 * 640 };
900 SDL_SetRenderDrawColor(d->render, 0, 0, 0, 255); 984 SDL_SetRenderDrawColor(d->render, 0, 0, 0, 255);
901 SDL_RenderFillRect(d->render, &rect); 985 SDL_RenderFillRect(d->render, &rect);
902 SDL_RenderCopy(d->render, glyphCache_Text(), NULL, &rect); 986 SDL_RenderCopy(d->render, glyphCache_Text(), NULL, &rect);
@@ -912,7 +996,7 @@ void resize_Window(iWindow *d, int w, int h) {
912 996
913void setTitle_Window(iWindow *d, const iString *title) { 997void setTitle_Window(iWindow *d, const iString *title) {
914 SDL_SetWindowTitle(d->win, cstr_String(title)); 998 SDL_SetWindowTitle(d->win, cstr_String(title));
915 iLabelWidget *bar = findChild_Widget(d->root.widget, "winbar.title"); 999 iLabelWidget *bar = findChild_Widget(get_Root()->widget, "winbar.title");
916 if (bar) { 1000 if (bar) {
917 updateText_LabelWidget(bar, title); 1001 updateText_LabelWidget(bar, title);
918 } 1002 }
diff --git a/src/ui/window.h b/src/ui/window.h
index a95d9f40..12c540d3 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -67,7 +67,7 @@ struct Impl_Window {
67 uint32_t focusGainedAt; 67 uint32_t focusGainedAt;
68 SDL_Renderer *render; 68 SDL_Renderer *render;
69 iInt2 size; 69 iInt2 size;
70 iRoot root; /* root widget and UI state */ 70 iRoot * roots[2]; /* root widget and UI state; second one is for split mode */
71 float pixelRatio; /* conversion between points and pixels, e.g., coords, window size */ 71 float pixelRatio; /* conversion between points and pixels, e.g., coords, window size */
72 float displayScale; /* DPI-based scaling factor of current display, affects uiScale only */ 72 float displayScale; /* DPI-based scaling factor of current display, affects uiScale only */
73 float uiScale; 73 float uiScale;
@@ -83,6 +83,7 @@ struct Impl_Window {
83}; 83};
84 84
85iBool processEvent_Window (iWindow *, const SDL_Event *); 85iBool processEvent_Window (iWindow *, const SDL_Event *);
86iBool dispatchEvent_Window (iWindow *, const SDL_Event *);
86void draw_Window (iWindow *); 87void draw_Window (iWindow *);
87void drawWhileResizing_Window(iWindow *d, int w, int h); /* workaround for SDL bug */ 88void drawWhileResizing_Window(iWindow *d, int w, int h); /* workaround for SDL bug */
88void resize_Window (iWindow *, int w, int h); 89void resize_Window (iWindow *, int w, int h);
@@ -101,10 +102,12 @@ iInt2 maxTextureSize_Window (const iWindow *);
101float uiScale_Window (const iWindow *); 102float uiScale_Window (const iWindow *);
102iInt2 coord_Window (const iWindow *, int x, int y); 103iInt2 coord_Window (const iWindow *, int x, int y);
103iInt2 mouseCoord_Window (const iWindow *); 104iInt2 mouseCoord_Window (const iWindow *);
105iAnyObject *hitChild_Window (const iWindow *, iInt2 coord);
104uint32_t frameTime_Window (const iWindow *); 106uint32_t frameTime_Window (const iWindow *);
105SDL_Renderer *renderer_Window (const iWindow *); 107SDL_Renderer *renderer_Window (const iWindow *);
106int snap_Window (const iWindow *); 108int snap_Window (const iWindow *);
107iBool isFullscreen_Window (const iWindow *); 109iBool isFullscreen_Window (const iWindow *);
110iRoot * findRoot_Window (const iWindow *, const iWidget *widget);
108 111
109iWindow * get_Window (void); 112iWindow * get_Window (void);
110 113