summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-04-28 14:02:20 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-04-28 14:02:20 +0300
commit408597bd4f71a13a511b6af33601dff0be2ed317 (patch)
tree44f6b776ddab24bb5d1b18179872546125c3d679 /src
parent852689943bfbbbb933ba617cb0a971f82923a2f3 (diff)
Working on multiple UI roots
Various refactorings and fixes to handle root-global and window-global state, root-specific palettes, and proper coordinate system changes (e.g., when opening menus). UI events are posted and handled in the context of a specific root.
Diffstat (limited to 'src')
-rw-r--r--src/app.c92
-rw-r--r--src/app.h10
-rw-r--r--src/history.c15
-rw-r--r--src/periodic.c5
-rw-r--r--src/ui/color.c21
-rw-r--r--src/ui/color.h11
-rw-r--r--src/ui/documentwidget.c51
-rw-r--r--src/ui/inputwidget.c6
-rw-r--r--src/ui/keys.c2
-rw-r--r--src/ui/lookupwidget.c14
-rw-r--r--src/ui/root.c17
-rw-r--r--src/ui/root.h6
-rw-r--r--src/ui/sidebarwidget.c12
-rw-r--r--src/ui/touch.c3
-rw-r--r--src/ui/util.c27
-rw-r--r--src/ui/util.h4
-rw-r--r--src/ui/widget.c107
-rw-r--r--src/ui/widget.h16
-rw-r--r--src/ui/window.c31
-rw-r--r--src/ui/window.h3
20 files changed, 269 insertions, 184 deletions
diff --git a/src/app.c b/src/app.c
index 39f399d1..85284d19 100644
--- a/src/app.c
+++ b/src/app.c
@@ -346,7 +346,7 @@ static void loadPrefs_App_(iApp *d) {
346 } 346 }
347#endif 347#endif
348 else { 348 else {
349 postCommandString_App(&cmdStr); 349 postCommandString_Root(NULL, &cmdStr);
350 } 350 }
351 deinit_String(&cmdStr); 351 deinit_String(&cmdStr);
352 } 352 }
@@ -399,18 +399,25 @@ static iBool loadState_App_(iApp *d) {
399 return iFalse; 399 return iFalse;
400 } 400 }
401 setVersion_Stream(stream_File(f), version); 401 setVersion_Stream(stream_File(f), version);
402 iDocumentWidget *doc = document_App(); /* first one is always from root 0 */ 402 iDocumentWidget *doc = NULL; // document_App(); /* first one is always from root 0 */
403 iDocumentWidget *current = NULL; 403 iBool isFirstTab[2] = { iTrue, iTrue };
404 iDocumentWidget *current[2] = { NULL, NULL };
404 while (!atEnd_File(f)) { 405 while (!atEnd_File(f)) {
405 readData_File(f, 4, magic); 406 readData_File(f, 4, magic);
406 if (!memcmp(magic, magicTabDocument_App_, 4)) { 407 if (!memcmp(magic, magicTabDocument_App_, 4)) {
407 const int8_t flags = read8_File(f); 408 const int8_t flags = read8_File(f);
408 if (!doc) { 409 const int rootIndex = flags & rootIndex1_DocumentStateFlag ? 1 : 0;
409 setCurrent_Root(d->window->roots[flags & rootIndex1_DocumentStateFlag ? 1 : 0]); 410 setCurrent_Root(d->window->roots[rootIndex]);
411 if (isFirstTab[rootIndex]) {
412 isFirstTab[rootIndex] = iFalse;
413 /* There is one pre-created tab in each root. */
414 doc = document_Root(get_Root());
415 }
416 else {
410 doc = newTab_App(NULL, iFalse /* no switching */); 417 doc = newTab_App(NULL, iFalse /* no switching */);
411 } 418 }
412 if (flags & current_DocumentStateFlag) { 419 if (flags & current_DocumentStateFlag) {
413 current = doc; 420 current[rootIndex] = doc;
414 } 421 }
415 deserializeState_DocumentWidget(doc, stream_File(f)); 422 deserializeState_DocumentWidget(doc, stream_File(f));
416 doc = NULL; 423 doc = NULL;
@@ -421,7 +428,9 @@ static iBool loadState_App_(iApp *d) {
421 return iFalse; 428 return iFalse;
422 } 429 }
423 } 430 }
424 postCommandf_App("tabs.switch page:%p", current); 431 iForIndices(i, current) {
432 postCommandf_Root(NULL, "tabs.switch page:%p", current[i]);
433 }
425 setCurrent_Root(NULL); 434 setCurrent_Root(NULL);
426 return iTrue; 435 return iTrue;
427 } 436 }
@@ -466,7 +475,7 @@ static uint32_t checkAsleep_App_(uint32_t interval, void *param) {
466 475
467static uint32_t postAutoReloadCommand_App_(uint32_t interval, void *param) { 476static uint32_t postAutoReloadCommand_App_(uint32_t interval, void *param) {
468 iUnused(param); 477 iUnused(param);
469 postCommand_App("document.autoreload"); 478 postCommand_Root(NULL, "document.autoreload");
470 return interval; 479 return interval;
471} 480}
472 481
@@ -660,7 +669,6 @@ static void init_App_(iApp *d, int argc, char **argv) {
660 d->bookmarks = new_Bookmarks(); 669 d->bookmarks = new_Bookmarks();
661 d->tabEnum = 0; /* generates unique IDs for tab pages */ 670 d->tabEnum = 0; /* generates unique IDs for tab pages */
662 init_Periodic(&d->periodic); 671 init_Periodic(&d->periodic);
663 setThemePalette_Color(d->prefs.theme);
664#if defined (iPlatformAppleDesktop) 672#if defined (iPlatformAppleDesktop)
665 setupApplication_MacOS(); 673 setupApplication_MacOS();
666#endif 674#endif
@@ -691,11 +699,12 @@ static void init_App_(iApp *d, int argc, char **argv) {
691 /* Widget state init. */ 699 /* Widget state init. */
692 processEvents_App(postedEventsOnly_AppEventMode); 700 processEvents_App(postedEventsOnly_AppEventMode);
693 if (!loadState_App_(d)) { 701 if (!loadState_App_(d)) {
694 postCommand_App("open url:about:help"); 702 postCommand_Root(NULL, "open url:about:help");
695 } 703 }
696 postCommand_App("window.unfreeze"); 704 //setThemePalette_Color(d->prefs.theme);
705 postCommand_Root(NULL, "window.unfreeze");
697 d->autoReloadTimer = SDL_AddTimer(60 * 1000, postAutoReloadCommand_App_, NULL); 706 d->autoReloadTimer = SDL_AddTimer(60 * 1000, postAutoReloadCommand_App_, NULL);
698 postCommand_App("document.autoreload"); 707 postCommand_Root(NULL, "document.autoreload");
699#if defined (LAGRANGE_ENABLE_IDLE_SLEEP) 708#if defined (LAGRANGE_ENABLE_IDLE_SLEEP)
700 d->isIdling = iFalse; 709 d->isIdling = iFalse;
701 d->lastEventTime = 0; 710 d->lastEventTime = 0;
@@ -704,12 +713,12 @@ static void init_App_(iApp *d, int argc, char **argv) {
704 d->isFinishedLaunching = iTrue; 713 d->isFinishedLaunching = iTrue;
705 /* Run any commands that were pending completion of launch. */ { 714 /* Run any commands that were pending completion of launch. */ {
706 iForEach(StringList, i, d->launchCommands) { 715 iForEach(StringList, i, d->launchCommands) {
707 postCommandString_App(i.value); 716 postCommandString_Root(NULL, i.value);
708 } 717 }
709 } 718 }
710 /* URLs from the command line. */ { 719 /* URLs from the command line. */ {
711 iConstForEach(StringList, i, openCmds) { 720 iConstForEach(StringList, i, openCmds) {
712 postCommandString_App(i.value); 721 postCommandString_Root(NULL, i.value);
713 } 722 }
714 iRelease(openCmds); 723 iRelease(openCmds);
715 } 724 }
@@ -824,7 +833,8 @@ const iString *debugInfo_App(void) {
824 appendFormat_String(msg, "## Documents\n"); 833 appendFormat_String(msg, "## Documents\n");
825 iForEach(ObjectList, k, iClob(listDocuments_App(NULL))) { 834 iForEach(ObjectList, k, iClob(listDocuments_App(NULL))) {
826 iDocumentWidget *doc = k.object; 835 iDocumentWidget *doc = k.object;
827 appendFormat_String(msg, "### Tab %zu: %s\n", 836 appendFormat_String(msg, "### Tab %d.%zu: %s\n",
837 constAs_Widget(doc)->root == get_Window()->roots[0] ? 0 : 1,
828 childIndex_Widget(constAs_Widget(doc)->parent, k.object), 838 childIndex_Widget(constAs_Widget(doc)->parent, k.object),
829 cstr_String(bookmarkTitle_DocumentWidget(doc))); 839 cstr_String(bookmarkTitle_DocumentWidget(doc)));
830 append_String(msg, collect_String(debugInfo_History(history_DocumentWidget(doc)))); 840 append_String(msg, collect_String(debugInfo_History(history_DocumentWidget(doc))));
@@ -918,7 +928,6 @@ void processEvents_App(enum iAppEventMode eventMode) {
918 iApp *d = &app_; 928 iApp *d = &app_;
919 SDL_Event ev; 929 SDL_Event ev;
920 iBool gotEvents = iFalse; 930 iBool gotEvents = iFalse;
921 dispatchCommands_Periodic(&d->periodic);
922 while (nextEvent_App_(d, eventMode, &ev)) { 931 while (nextEvent_App_(d, eventMode, &ev)) {
923#if defined (iPlatformAppleMobile) 932#if defined (iPlatformAppleMobile)
924 if (processEvent_iOS(&ev)) { 933 if (processEvent_iOS(&ev)) {
@@ -930,7 +939,7 @@ void processEvents_App(enum iAppEventMode eventMode) {
930 d->isRunning = iFalse; 939 d->isRunning = iFalse;
931 if (findWidget_App("prefs")) { 940 if (findWidget_App("prefs")) {
932 /* Make sure changed preferences get saved. */ 941 /* Make sure changed preferences get saved. */
933 postCommand_App("prefs.dismiss"); 942 postCommand_Root(NULL, "prefs.dismiss");
934 processEvents_App(postedEventsOnly_AppEventMode); 943 processEvents_App(postedEventsOnly_AppEventMode);
935 } 944 }
936 goto backToMainLoop; 945 goto backToMainLoop;
@@ -964,10 +973,10 @@ void processEvents_App(enum iAppEventMode eventMode) {
964 if (startsWithCase_CStr(ev.drop.file, "gemini:") || 973 if (startsWithCase_CStr(ev.drop.file, "gemini:") ||
965 startsWithCase_CStr(ev.drop.file, "gopher:") || 974 startsWithCase_CStr(ev.drop.file, "gopher:") ||
966 startsWithCase_CStr(ev.drop.file, "file:")) { 975 startsWithCase_CStr(ev.drop.file, "file:")) {
967 postCommandf_App("~open newtab:%d url:%s", newTab, ev.drop.file); 976 postCommandf_Root(NULL, "~open newtab:%d url:%s", newTab, ev.drop.file);
968 } 977 }
969 else { 978 else {
970 postCommandf_App( 979 postCommandf_Root(NULL,
971 "~open newtab:%d url:%s", newTab, makeFileUrl_CStr(ev.drop.file)); 980 "~open newtab:%d url:%s", newTab, makeFileUrl_CStr(ev.drop.file));
972 } 981 }
973 } 982 }
@@ -1094,7 +1103,7 @@ static void runTickers_App_(iApp *d) {
1094 iConstForEach(Array, i, &pending->values) { 1103 iConstForEach(Array, i, &pending->values) {
1095 const iTicker *ticker = i.value; 1104 const iTicker *ticker = i.value;
1096 if (ticker->callback) { 1105 if (ticker->callback) {
1097 setCurrent_Root(findRoot_Window(d->window, ticker->context)); 1106 setCurrent_Root(findRoot_Window(d->window, ticker->context)); /* root might be NULL */
1098 ticker->callback(ticker->context); 1107 ticker->callback(ticker->context);
1099 } 1108 }
1100 } 1109 }
@@ -1135,6 +1144,7 @@ static int run_App_(iApp *d) {
1135 SDL_AddEventWatch(resizeWatcher_, d); /* redraw window during resizing */ 1144 SDL_AddEventWatch(resizeWatcher_, d); /* redraw window during resizing */
1136#endif 1145#endif
1137 while (d->isRunning) { 1146 while (d->isRunning) {
1147 dispatchCommands_Periodic(&d->periodic);
1138 processEvents_App(waitForNewEvents_AppEventMode); 1148 processEvents_App(waitForNewEvents_AppEventMode);
1139 setCurrent_Root(NULL); 1149 setCurrent_Root(NULL);
1140 runTickers_App_(d); 1150 runTickers_App_(d);
@@ -1242,8 +1252,7 @@ void postRefresh_App(void) {
1242 } 1252 }
1243} 1253}
1244 1254
1245void postCommand_App(const char *command) { 1255void postCommand_Root(iRoot *d, const char *command) {
1246 iApp *d = &app_;
1247 iAssert(command); 1256 iAssert(command);
1248 if (strlen(command) == 0) { 1257 if (strlen(command) == 0) {
1249 return; 1258 return;
@@ -1255,8 +1264,8 @@ void postCommand_App(const char *command) {
1255 if (*command == '~') { 1264 if (*command == '~') {
1256 /* Requires launch to be finished; defer it if needed. */ 1265 /* Requires launch to be finished; defer it if needed. */
1257 command++; 1266 command++;
1258 if (!d->isFinishedLaunching) { 1267 if (!app_.isFinishedLaunching) {
1259 pushBackCStr_StringList(d->launchCommands, command); 1268 pushBackCStr_StringList(app_.launchCommands, command);
1260 return; 1269 return;
1261 } 1270 }
1262 } 1271 }
@@ -1264,12 +1273,26 @@ void postCommand_App(const char *command) {
1264 ev.user.code = command_UserEventCode; 1273 ev.user.code = command_UserEventCode;
1265 /*ev.user.windowID = id_Window(get_Window());*/ 1274 /*ev.user.windowID = id_Window(get_Window());*/
1266 ev.user.data1 = strdup(command); 1275 ev.user.data1 = strdup(command);
1276 ev.user.data2 = d; /* all events are root-specific */
1267 SDL_PushEvent(&ev); 1277 SDL_PushEvent(&ev);
1268 if (app_.commandEcho) { 1278 if (app_.commandEcho) {
1269 printf("[command] %s\n", command); fflush(stdout); 1279 printf("[command] {%d} %s\n",
1280 (d == NULL ? 0 : d == get_Window()->roots[0] ? 1 : 2),
1281 command); fflush(stdout);
1270 } 1282 }
1271} 1283}
1272 1284
1285void postCommandf_Root(iRoot *d, const char *command, ...) {
1286 iBlock chars;
1287 init_Block(&chars, 0);
1288 va_list args;
1289 va_start(args, command);
1290 vprintf_Block(&chars, command, args);
1291 va_end(args);
1292 postCommand_Root(d, cstr_Block(&chars));
1293 deinit_Block(&chars);
1294}
1295
1273void postCommandf_App(const char *command, ...) { 1296void postCommandf_App(const char *command, ...) {
1274 iBlock chars; 1297 iBlock chars;
1275 init_Block(&chars, 0); 1298 init_Block(&chars, 0);
@@ -1277,16 +1300,23 @@ void postCommandf_App(const char *command, ...) {
1277 va_start(args, command); 1300 va_start(args, command);
1278 vprintf_Block(&chars, command, args); 1301 vprintf_Block(&chars, command, args);
1279 va_end(args); 1302 va_end(args);
1280 postCommand_App(cstr_Block(&chars)); 1303 postCommand_Root(NULL, cstr_Block(&chars));
1281 deinit_Block(&chars); 1304 deinit_Block(&chars);
1282} 1305}
1283 1306
1307void rootOrder_App(iRoot *roots[2]) {
1308 const iWindow *win = app_.window;
1309 roots[0] = get_Root();
1310 roots[1] = (roots[0] == win->roots[0] ? win->roots[1] : win->roots[0]);
1311}
1312
1284iAny *findWidget_App(const char *id) { 1313iAny *findWidget_App(const char *id) {
1285 if (!*id) return NULL; 1314 if (!*id) return NULL;
1286 iForIndices(i, app_.window->roots) { 1315 iRoot *order[2];
1287 iRoot *root = app_.window->roots[i]; 1316 rootOrder_App(order);
1288 if (root) { 1317 iForIndices(i, order) {
1289 iAny *found = findChild_Widget(root->widget, id); 1318 if (order[i]) {
1319 iAny *found = findChild_Widget(order[i]->widget, id);
1290 if (found) { 1320 if (found) {
1291 return found; 1321 return found;
1292 } 1322 }
@@ -1485,7 +1515,7 @@ iDocumentWidget *document_Command(const char *cmd) {
1485 1515
1486iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNew) { 1516iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNew) {
1487 iApp *d = &app_; 1517 iApp *d = &app_;
1488 iWidget *tabs = findWidget_App("doctabs"); 1518 iWidget *tabs = findWidget_Root("doctabs");
1489 setFlags_Widget(tabs, hidden_WidgetFlag, iFalse); 1519 setFlags_Widget(tabs, hidden_WidgetFlag, iFalse);
1490 iWidget *newTabButton = findChild_Widget(tabs, "newtab"); 1520 iWidget *newTabButton = findChild_Widget(tabs, "newtab");
1491 removeChild_Widget(newTabButton->parent, newTabButton); 1521 removeChild_Widget(newTabButton->parent, newTabButton);
diff --git a/src/app.h b/src/app.h
index 0012d979..1b8360b3 100644
--- a/src/app.h
+++ b/src/app.h
@@ -107,14 +107,18 @@ iAny * findWidget_App (const char *id);
107void addTicker_App (iTickerFunc ticker, iAny *context); 107void addTicker_App (iTickerFunc ticker, iAny *context);
108void removeTicker_App (iTickerFunc ticker, iAny *context); 108void removeTicker_App (iTickerFunc ticker, iAny *context);
109void postRefresh_App (void); 109void postRefresh_App (void);
110void postCommand_App (const char *command); 110void postCommand_Root (iRoot *, const char *command);
111void postCommandf_Root (iRoot *, const char *command, ...);
111void postCommandf_App (const char *command, ...); 112void postCommandf_App (const char *command, ...);
112 113
113iLocalDef void postCommandString_App(const iString *command) { 114iLocalDef void postCommandString_Root(iRoot *d, const iString *command) {
114 if (command) { 115 if (command) {
115 postCommand_App(cstr_String(command)); 116 postCommand_Root(d, cstr_String(command));
116 } 117 }
117} 118}
119iLocalDef void postCommand_App(const char *command) {
120 postCommandf_App(command);
121}
118 122
119iDocumentWidget * document_Command (const char *cmd); 123iDocumentWidget * document_Command (const char *cmd);
120 124
diff --git a/src/history.c b/src/history.c
index 48820f6d..9f4e415b 100644
--- a/src/history.c
+++ b/src/history.c
@@ -21,6 +21,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include "history.h" 23#include "history.h"
24#include "ui/root.h"
24#include "app.h" 25#include "app.h"
25 26
26#include <the_Foundation/file.h> 27#include <the_Foundation/file.h>
@@ -244,9 +245,10 @@ iBool goBack_History(iHistory *d) {
244 lock_Mutex(d->mtx); 245 lock_Mutex(d->mtx);
245 if (d->recentPos < size_Array(&d->recent) - 1) { 246 if (d->recentPos < size_Array(&d->recent) - 1) {
246 d->recentPos++; 247 d->recentPos++;
247 postCommandf_App("open history:1 scroll:%f url:%s", 248 postCommandf_Root(get_Root(),
248 mostRecentUrl_History(d)->normScrollY, 249 "open history:1 scroll:%f url:%s",
249 cstr_String(url_History(d, d->recentPos))); 250 mostRecentUrl_History(d)->normScrollY,
251 cstr_String(url_History(d, d->recentPos)));
250 unlock_Mutex(d->mtx); 252 unlock_Mutex(d->mtx);
251 return iTrue; 253 return iTrue;
252 } 254 }
@@ -258,9 +260,10 @@ iBool goForward_History(iHistory *d) {
258 lock_Mutex(d->mtx); 260 lock_Mutex(d->mtx);
259 if (d->recentPos > 0) { 261 if (d->recentPos > 0) {
260 d->recentPos--; 262 d->recentPos--;
261 postCommandf_App("open history:1 scroll:%f url:%s", 263 postCommandf_Root(get_Root(),
262 mostRecentUrl_History(d)->normScrollY, 264 "open history:1 scroll:%f url:%s",
263 cstr_String(url_History(d, d->recentPos))); 265 mostRecentUrl_History(d)->normScrollY,
266 cstr_String(url_History(d, d->recentPos)));
264 unlock_Mutex(d->mtx); 267 unlock_Mutex(d->mtx);
265 return iTrue; 268 return iTrue;
266 } 269 }
diff --git a/src/periodic.c b/src/periodic.c
index eb4fb98d..0291ff98 100644
--- a/src/periodic.c
+++ b/src/periodic.c
@@ -70,10 +70,11 @@ iBool dispatchCommands_Periodic(iPeriodic *d) {
70 const SDL_UserEvent ev = { 70 const SDL_UserEvent ev = {
71 .type = SDL_USEREVENT, 71 .type = SDL_USEREVENT,
72 .code = command_UserEventCode, 72 .code = command_UserEventCode,
73 .data1 = (void *) cstr_String(&pc->command) 73 .data1 = (void *) cstr_String(&pc->command),
74 .data2 = findRoot_Window(get_Window(), pc->context)
74 }; 75 };
75 iAssert(isInstance_Object(pc->context, &Class_Widget)); 76 iAssert(isInstance_Object(pc->context, &Class_Widget));
76 setCurrent_Root(findRoot_Window(get_Window(), pc->context)); 77 setCurrent_Root(ev.data2);
77 dispatchEvent_Widget(pc->context, (const SDL_Event *) &ev); 78 dispatchEvent_Widget(pc->context, (const SDL_Event *) &ev);
78 wasPosted = iTrue; 79 wasPosted = iTrue;
79 } 80 }
diff --git a/src/ui/color.c b/src/ui/color.c
index bcecb529..6cbbdf28 100644
--- a/src/ui/color.c
+++ b/src/ui/color.c
@@ -21,6 +21,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include "color.h" 23#include "color.h"
24#include "root.h"
24#include "app.h" 25#include "app.h"
25 26
26#include <the_Foundation/string.h> 27#include <the_Foundation/string.h>
@@ -65,7 +66,11 @@ static const iColor lightPalette_[] = {
65 { 0, 150, 0, 255 }, 66 { 0, 150, 0, 255 },
66}; 67};
67 68
68static iColor palette_[max_ColorId]; 69static iColor uiPalette_[tmFirst_ColorId]; /* not theme-specific */
70
71iColor *paletteColor_(enum iColorId id) {
72 return id < tmFirst_ColorId ? &uiPalette_[id] : &get_Root()->tmPalette[id - tmFirst_ColorId];
73}
69 74
70iLocalDef void copy_(enum iColorId dst, enum iColorId src) { 75iLocalDef void copy_(enum iColorId dst, enum iColorId src) {
71 set_Color(dst, get_Color(src)); 76 set_Color(dst, get_Color(src));
@@ -73,7 +78,7 @@ iLocalDef void copy_(enum iColorId dst, enum iColorId src) {
73 78
74void setThemePalette_Color(enum iColorTheme theme) { 79void setThemePalette_Color(enum iColorTheme theme) {
75 const iPrefs *prefs = prefs_App(); 80 const iPrefs *prefs = prefs_App();
76 memcpy(palette_, isDark_ColorTheme(theme) ? darkPalette_ : lightPalette_, sizeof(darkPalette_)); 81 memcpy(uiPalette_, isDark_ColorTheme(theme) ? darkPalette_ : lightPalette_, sizeof(darkPalette_));
77 const int accentHi = (prefs->accent == cyan_ColorAccent ? cyan_ColorId : orange_ColorId); 82 const int accentHi = (prefs->accent == cyan_ColorAccent ? cyan_ColorId : orange_ColorId);
78 const int accentLo = (prefs->accent == cyan_ColorAccent ? teal_ColorId : brown_ColorId); 83 const int accentLo = (prefs->accent == cyan_ColorAccent ? teal_ColorId : brown_ColorId);
79 const int altAccentHi = (prefs->accent == cyan_ColorAccent ? orange_ColorId : cyan_ColorId); 84 const int altAccentHi = (prefs->accent == cyan_ColorAccent ? orange_ColorId : cyan_ColorId);
@@ -298,8 +303,8 @@ void setThemePalette_Color(enum iColorTheme theme) {
298 set_Color(uiTextShortcut_ColorId, mix_Color(get_Color(uiTextShortcut_ColorId), 303 set_Color(uiTextShortcut_ColorId, mix_Color(get_Color(uiTextShortcut_ColorId),
299 get_Color(uiBackground_ColorId), 304 get_Color(uiBackground_ColorId),
300 0.4f)); 305 0.4f));
301 palette_[uiMarked_ColorId].a = 128; 306 uiPalette_[uiMarked_ColorId ].a = 128;
302 palette_[uiMatching_ColorId].a = 128; 307 uiPalette_[uiMatching_ColorId].a = 128;
303 if (deviceType_App() == phone_AppDeviceType) { 308 if (deviceType_App() == phone_AppDeviceType) {
304 copy_(uiInputBackground_ColorId, uiBackgroundSidebar_ColorId); 309 copy_(uiInputBackground_ColorId, uiBackgroundSidebar_ColorId);
305 copy_(uiInputFrame_ColorId, uiBackgroundSidebar_ColorId); 310 copy_(uiInputFrame_ColorId, uiBackgroundSidebar_ColorId);
@@ -311,14 +316,14 @@ void setThemePalette_Color(enum iColorTheme theme) {
311iColor get_Color(int color) { 316iColor get_Color(int color) {
312 const iColor *rgba = &transparent_; 317 const iColor *rgba = &transparent_;
313 if (color >= 0 && color < max_ColorId) { 318 if (color >= 0 && color < max_ColorId) {
314 rgba = &palette_[color]; 319 rgba = paletteColor_(color);
315 } 320 }
316 return *rgba; 321 return *rgba;
317} 322}
318 323
319void set_Color(int color, iColor rgba) { 324void set_Color(int color, iColor rgba) {
320 if (color >= uiBackground_ColorId && color < max_ColorId) { 325 if (color >= uiBackground_ColorId && color < max_ColorId) {
321 palette_[color] = rgba; 326 *paletteColor_(color) = rgba;
322 } 327 }
323} 328}
324 329
@@ -341,7 +346,7 @@ iLocalDef iBool equal_Color_(const iColor *x, const iColor *y) {
341int darker_Color(int color) { 346int darker_Color(int color) {
342 const iColor rgb = get_Color(color); 347 const iColor rgb = get_Color(color);
343 for (int i = 0; i < uiFirst_ColorId; i++) { 348 for (int i = 0; i < uiFirst_ColorId; i++) {
344 if (equal_Color_(&rgb, &palette_[i])) { 349 if (equal_Color_(&rgb, paletteColor_(i))) {
345 return i > 0 ? i - 1 : i; 350 return i > 0 ? i - 1 : i;
346 } 351 }
347 } 352 }
@@ -351,7 +356,7 @@ int darker_Color(int color) {
351int lighter_Color(int color) { 356int lighter_Color(int color) {
352 const iColor rgb = get_Color(color); 357 const iColor rgb = get_Color(color);
353 for (int i = 0; i < uiFirst_ColorId; i++) { 358 for (int i = 0; i < uiFirst_ColorId; i++) {
354 if (equal_Color_(&rgb, &palette_[i])) { 359 if (equal_Color_(&rgb, paletteColor_(i))) {
355 return i < uiFirst_ColorId - 1 ? i + 1 : i; 360 return i < uiFirst_ColorId - 1 ? i + 1 : i;
356 } 361 }
357 } 362 }
diff --git a/src/ui/color.h b/src/ui/color.h
index cd2f95d3..c0db4382 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -115,9 +115,7 @@ enum iColorId {
115 uiTextAppTitle_ColorId, 115 uiTextAppTitle_ColorId,
116 uiBackgroundSidebar_ColorId, 116 uiBackgroundSidebar_ColorId,
117 uiBackgroundMenu_ColorId, 117 uiBackgroundMenu_ColorId,
118 tmLinkCustomIconVisited_ColorId, /* derived from other theme colors */ 118
119 tmAltTextBackground_ColorId, /* derived from other theme colors */
120
121 /* content theme colors */ 119 /* content theme colors */
122 tmFirst_ColorId, 120 tmFirst_ColorId,
123 tmBackground_ColorId = tmFirst_ColorId, 121 tmBackground_ColorId = tmFirst_ColorId,
@@ -133,9 +131,9 @@ enum iColorId {
133 tmBannerTitle_ColorId, 131 tmBannerTitle_ColorId,
134 tmBannerIcon_ColorId, 132 tmBannerIcon_ColorId,
135 tmBannerSideTitle_ColorId, 133 tmBannerSideTitle_ColorId,
136 tmOutlineHeadingAbove_ColorId,
137 tmOutlineHeadingBelow_ColorId,
138 tmInlineContentMetadata_ColorId, 134 tmInlineContentMetadata_ColorId,
135 tmAltTextBackground_ColorId, /* derived from other theme colors */
136 tmLinkCustomIconVisited_ColorId, /* derived from other theme colors */
139 tmBadLink_ColorId, 137 tmBadLink_ColorId,
140 138
141 tmLinkIcon_ColorId, 139 tmLinkIcon_ColorId,
@@ -159,7 +157,8 @@ enum iColorId {
159 tmGopherLinkDomain_ColorId, 157 tmGopherLinkDomain_ColorId,
160 tmGopherLinkLastVisitDate_ColorId, 158 tmGopherLinkLastVisitDate_ColorId,
161 159
162 max_ColorId 160 max_ColorId,
161 tmMax_ColorId = max_ColorId - tmFirst_ColorId
163}; 162};
164 163
165iLocalDef iBool isLink_ColorId(enum iColorId d) { 164iLocalDef iBool isLink_ColorId(enum iColorId d) {
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 79678f56..2f20958f 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1234,7 +1234,10 @@ static void fetch_DocumentWidget_(iDocumentWidget *d) {
1234 iRelease(d->request); 1234 iRelease(d->request);
1235 d->request = NULL; 1235 d->request = NULL;
1236 } 1236 }
1237 postCommandf_App("document.request.started doc:%p url:%s", d, cstr_String(d->mod.url)); 1237 postCommandf_Root(as_Widget(d)->root,
1238 "document.request.started doc:%p url:%s",
1239 d,
1240 cstr_String(d->mod.url));
1238 clear_ObjectList(d->media); 1241 clear_ObjectList(d->media);
1239 d->certFlags = 0; 1242 d->certFlags = 0;
1240 setLinkNumberMode_DocumentWidget_(d, iFalse); 1243 setLinkNumberMode_DocumentWidget_(d, iFalse);
@@ -1318,7 +1321,7 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1318 updateVisible_DocumentWidget_(d); 1321 updateVisible_DocumentWidget_(d);
1319 moveSpan_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */ 1322 moveSpan_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */
1320 cacheDocumentGlyphs_DocumentWidget_(d); 1323 cacheDocumentGlyphs_DocumentWidget_(d);
1321 postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); 1324 postCommandf_Root(as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
1322 return iTrue; 1325 return iTrue;
1323 } 1326 }
1324 else if (!isEmpty_String(d->mod.url)) { 1327 else if (!isEmpty_String(d->mod.url)) {
@@ -1391,7 +1394,7 @@ static void scrollToHeading_DocumentWidget_(iDocumentWidget *d, const char *head
1391 iConstForEach(Array, h, headings_GmDocument(d->doc)) { 1394 iConstForEach(Array, h, headings_GmDocument(d->doc)) {
1392 const iGmHeading *head = h.value; 1395 const iGmHeading *head = h.value;
1393 if (startsWithCase_Rangecc(head->text, heading)) { 1396 if (startsWithCase_Rangecc(head->text, heading)) {
1394 postCommandf_App("document.goto loc:%p", head->text.start); 1397 postCommandf_Root(as_Widget(d)->root, "document.goto loc:%p", head->text.start);
1395 break; 1398 break;
1396 } 1399 }
1397 } 1400 }
@@ -1514,7 +1517,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1514 cstr_Rangecc(urlScheme_String(d->mod.url)))) { 1517 cstr_Rangecc(urlScheme_String(d->mod.url)))) {
1515 /* Redirects with the same scheme are automatic. */ 1518 /* Redirects with the same scheme are automatic. */
1516 visitUrl_Visited(visited_App(), d->mod.url, transient_VisitedUrlFlag); 1519 visitUrl_Visited(visited_App(), d->mod.url, transient_VisitedUrlFlag);
1517 postCommandf_App( 1520 postCommandf_Root(as_Widget(d)->root,
1518 "open doc:%p redirect:%d url:%s", d, d->redirectCount + 1, cstr_String(dstUrl)); 1521 "open doc:%p redirect:%d url:%s", d, d->redirectCount + 1, cstr_String(dstUrl));
1519 } 1522 }
1520 else { 1523 else {
@@ -2037,7 +2040,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2037 if (!isEmpty_Block(d->certFingerprint) && !isEmpty_Range(&host)) { 2040 if (!isEmpty_Block(d->certFingerprint) && !isEmpty_Range(&host)) {
2038 setTrusted_GmCerts(certs_App(), host, d->certFingerprint, &d->certExpiry); 2041 setTrusted_GmCerts(certs_App(), host, d->certFingerprint, &d->certExpiry);
2039 d->certFlags |= trusted_GmCertFlag; 2042 d->certFlags |= trusted_GmCertFlag;
2040 postCommand_App("document.info"); 2043 postCommand_Widget(w, "document.info");
2041 updateTrust_DocumentWidget_(d, NULL); 2044 updateTrust_DocumentWidget_(d, NULL);
2042 redoLayout_GmDocument(d->doc); 2045 redoLayout_GmDocument(d->doc);
2043 invalidate_DocumentWidget_(d); 2046 invalidate_DocumentWidget_(d);
@@ -2065,7 +2068,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2065 SDL_SetClipboardText(cstr_String(copied)); 2068 SDL_SetClipboardText(cstr_String(copied));
2066 delete_String(copied); 2069 delete_String(copied);
2067 if (flags_Widget(w) & touchDrag_WidgetFlag) { 2070 if (flags_Widget(w) & touchDrag_WidgetFlag) {
2068 postCommand_App("document.select arg:0"); 2071 postCommand_Widget(w, "document.select arg:0");
2069 } 2072 }
2070 return iTrue; 2073 return iTrue;
2071 } 2074 }
@@ -2102,13 +2105,13 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2102 } 2105 }
2103 appendCStr_String(url, "?"); 2106 appendCStr_String(url, "?");
2104 append_String(url, value); 2107 append_String(url, value);
2105 postCommandf_App("open url:%s", cstr_String(url)); 2108 postCommandf_Root(w->root, "open url:%s", cstr_String(url));
2106 delete_String(value); 2109 delete_String(value);
2107 return iTrue; 2110 return iTrue;
2108 } 2111 }
2109 else if (equal_Command(cmd, "valueinput.cancelled") && 2112 else if (equal_Command(cmd, "valueinput.cancelled") &&
2110 equal_Rangecc(range_Command(cmd, "id"), "document.input.submit") && document_App() == d) { 2113 equal_Rangecc(range_Command(cmd, "id"), "document.input.submit") && document_App() == d) {
2111 postCommand_App("navigate.back"); 2114 postCommand_Root(get_Root(), "navigate.back");
2112 return iTrue; 2115 return iTrue;
2113 } 2116 }
2114 else if (equalWidget_Command(cmd, w, "document.request.updated") && 2117 else if (equalWidget_Command(cmd, w, "document.request.updated") &&
@@ -2149,7 +2152,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2149 iReleasePtr(&d->request); 2152 iReleasePtr(&d->request);
2150 updateVisible_DocumentWidget_(d); 2153 updateVisible_DocumentWidget_(d);
2151 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; 2154 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag;
2152 postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); 2155 postCommandf_Root(w->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
2153 /* Check for a pending goto. */ 2156 /* Check for a pending goto. */
2154 if (!isEmpty_String(&d->pendingGotoHeading)) { 2157 if (!isEmpty_String(&d->pendingGotoHeading)) {
2155 scrollToHeading_DocumentWidget_(d, cstr_String(&d->pendingGotoHeading)); 2158 scrollToHeading_DocumentWidget_(d, cstr_String(&d->pendingGotoHeading));
@@ -2193,12 +2196,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2193 } 2196 }
2194 else if (equal_Command(cmd, "document.stop") && document_App() == d) { 2197 else if (equal_Command(cmd, "document.stop") && document_App() == d) {
2195 if (d->request) { 2198 if (d->request) {
2196 postCommandf_App( 2199 postCommandf_Root(w->root,
2197 "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url)); 2200 "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url));
2198 iReleasePtr(&d->request); 2201 iReleasePtr(&d->request);
2199 if (d->state != ready_RequestState) { 2202 if (d->state != ready_RequestState) {
2200 d->state = ready_RequestState; 2203 d->state = ready_RequestState;
2201 postCommand_App("navigate.back"); 2204 postCommand_Root(w->root, "navigate.back");
2202 } 2205 }
2203 updateFetchProgress_DocumentWidget_(d); 2206 updateFetchProgress_DocumentWidget_(d);
2204 return iTrue; 2207 return iTrue;
@@ -2267,7 +2270,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2267 } 2270 }
2268 else if (equal_Command(cmd, "navigate.back") && document_App() == d) { 2271 else if (equal_Command(cmd, "navigate.back") && document_App() == d) {
2269 if (d->request) { 2272 if (d->request) {
2270 postCommandf_App( 2273 postCommandf_Root(w->root,
2271 "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url)); 2274 "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url));
2272 iReleasePtr(&d->request); 2275 iReleasePtr(&d->request);
2273 updateFetchProgress_DocumentWidget_(d); 2276 updateFetchProgress_DocumentWidget_(d);
@@ -2291,14 +2294,14 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2291 if (parts.path.end[-1] == '/') break; 2294 if (parts.path.end[-1] == '/') break;
2292 parts.path.end--; 2295 parts.path.end--;
2293 } 2296 }
2294 postCommandf_App( 2297 postCommandf_Root(w->root,
2295 "open url:%s", 2298 "open url:%s",
2296 cstr_Rangecc((iRangecc){ constBegin_String(d->mod.url), parts.path.end })); 2299 cstr_Rangecc((iRangecc){ constBegin_String(d->mod.url), parts.path.end }));
2297 } 2300 }
2298 return iTrue; 2301 return iTrue;
2299 } 2302 }
2300 else if (equal_Command(cmd, "navigate.root") && document_App() == d) { 2303 else if (equal_Command(cmd, "navigate.root") && document_App() == d) {
2301 postCommandf_App("open url:%s/", cstr_Rangecc(urlRoot_String(d->mod.url))); 2304 postCommandf_Root(w->root, "open url:%s/", cstr_Rangecc(urlRoot_String(d->mod.url)));
2302 return iTrue; 2305 return iTrue;
2303 } 2306 }
2304 else if (equalWidget_Command(cmd, w, "scroll.moved")) { 2307 else if (equalWidget_Command(cmd, w, "scroll.moved")) {
@@ -2390,7 +2393,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2390 } 2393 }
2391 } 2394 }
2392 if (flags_Widget(w) & touchDrag_WidgetFlag) { 2395 if (flags_Widget(w) & touchDrag_WidgetFlag) {
2393 postCommand_App("document.select arg:0"); /* we can't handle both at the same time */ 2396 postCommand_Root(w->root, "document.select arg:0"); /* we can't handle both at the same time */
2394 } 2397 }
2395 invalidateWideRunsWithNonzeroOffset_DocumentWidget_(d); /* markers don't support offsets */ 2398 invalidateWideRunsWithNonzeroOffset_DocumentWidget_(d); /* markers don't support offsets */
2396 resetWideRuns_DocumentWidget_(d); 2399 resetWideRuns_DocumentWidget_(d);
@@ -2593,8 +2596,7 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev
2593 { cstrCollect_String(metadataLabel_Player(plr)), 0, 0, NULL }, 2596 { cstrCollect_String(metadataLabel_Player(plr)), 0, 0, NULL },
2594 }, 2597 },
2595 1); 2598 1);
2596 openMenu_Widget(d->playerMenu, 2599 openMenu_Widget(d->playerMenu, bottomLeft_Rect(ui.menuRect));
2597 localCoord_Widget(constAs_Widget(d), bottomLeft_Rect(ui.menuRect)));
2598 return iTrue; 2600 return iTrue;
2599 } 2601 }
2600 } 2602 }
@@ -2699,7 +2701,8 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2699 d->hoverLink = run; 2701 d->hoverLink = run;
2700 } 2702 }
2701 else { 2703 else {
2702 postCommandf_App("open newtab:%d url:%s", 2704 postCommandf_Root(w->root,
2705 "open newtab:%d url:%s",
2703 d->ordinalMode == 2706 d->ordinalMode ==
2704 numbersAndAlphabet_DocumentLinkOrdinalMode 2707 numbersAndAlphabet_DocumentLinkOrdinalMode
2705 ? openTabMode_Sym(modState_Keys()) 2708 ? openTabMode_Sym(modState_Keys())
@@ -2813,15 +2816,15 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2813 } 2816 }
2814 if (ev->type == SDL_MOUSEBUTTONDOWN) { 2817 if (ev->type == SDL_MOUSEBUTTONDOWN) {
2815 if (ev->button.button == SDL_BUTTON_X1) { 2818 if (ev->button.button == SDL_BUTTON_X1) {
2816 postCommand_App("navigate.back"); 2819 postCommand_Root(w->root, "navigate.back");
2817 return iTrue; 2820 return iTrue;
2818 } 2821 }
2819 if (ev->button.button == SDL_BUTTON_X2) { 2822 if (ev->button.button == SDL_BUTTON_X2) {
2820 postCommand_App("navigate.forward"); 2823 postCommand_Root(w->root, "navigate.forward");
2821 return iTrue; 2824 return iTrue;
2822 } 2825 }
2823 if (ev->button.button == SDL_BUTTON_MIDDLE && d->hoverLink) { 2826 if (ev->button.button == SDL_BUTTON_MIDDLE && d->hoverLink) {
2824 postCommandf_App("open newtab:%d url:%s", 2827 postCommandf_Root(w->root, "open newtab:%d url:%s",
2825 modState_Keys() & KMOD_SHIFT ? 1 : 2, 2828 modState_Keys() & KMOD_SHIFT ? 1 : 2,
2826 cstr_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId))); 2829 cstr_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId)));
2827 return iTrue; 2830 return iTrue;
@@ -2967,7 +2970,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2967 }, 2970 },
2968 3); 2971 3);
2969#endif 2972#endif
2970 postCommand_App("document.select arg:1"); 2973 postCommand_Root(w->root, "document.select arg:1");
2971 return iTrue; 2974 return iTrue;
2972 } 2975 }
2973 d->menu = makeMenu_Widget(w, data_Array(&items), size_Array(&items)); 2976 d->menu = makeMenu_Widget(w, data_Array(&items), size_Array(&items));
@@ -3188,7 +3191,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3188 refresh_Widget(w); 3191 refresh_Widget(w);
3189 } 3192 }
3190 else if (linkFlags & supportedProtocol_GmLinkFlag) { 3193 else if (linkFlags & supportedProtocol_GmLinkFlag) {
3191 postCommandf_App("open newtab:%d url:%s", 3194 postCommandf_Root(w->root, "open newtab:%d url:%s",
3192 openTabMode_Sym(modState_Keys()), 3195 openTabMode_Sym(modState_Keys()),
3193 cstr_String(absoluteUrl_String( 3196 cstr_String(absoluteUrl_String(
3194 d->mod.url, linkUrl_GmDocument(d->doc, linkId)))); 3197 d->mod.url, linkUrl_GmDocument(d->doc, linkId))));
@@ -3221,7 +3224,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3221 if (bannerType_DocumentWidget_(d) == certificateWarning_GmDocumentBanner && 3224 if (bannerType_DocumentWidget_(d) == certificateWarning_GmDocumentBanner &&
3222 pos_Click(&d->click).y - top_Rect(banRect) > 3225 pos_Click(&d->click).y - top_Rect(banRect) >
3223 lineHeight_Text(banner_FontId) * 2) { 3226 lineHeight_Text(banner_FontId) * 2) {
3224 postCommand_App("document.info"); 3227 postCommand_Widget(d, "document.info");
3225 } 3228 }
3226 else { 3229 else {
3227 postCommand_Widget(d, "navigate.root"); 3230 postCommand_Widget(d, "navigate.root");
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 9d360f47..a40b77eb 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -754,10 +754,10 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
754 return iTrue; 754 return iTrue;
755 } 755 }
756 if (ev->type == SDL_MOUSEMOTION && isHover_Widget(d)) { 756 if (ev->type == SDL_MOUSEMOTION && isHover_Widget(d)) {
757 const iInt2 local = localCoord_Widget(w, init_I2(ev->motion.x, ev->motion.y)); 757 const iInt2 inner = windowToInner_Widget(w, init_I2(ev->motion.x, ev->motion.y));
758 setCursor_Window(get_Window(), 758 setCursor_Window(get_Window(),
759 local.x >= 2 * gap_UI + d->leftPadding && 759 inner.x >= 2 * gap_UI + d->leftPadding &&
760 local.x < width_Widget(w) - d->rightPadding 760 inner.x < width_Widget(w) - d->rightPadding
761 ? SDL_SYSTEM_CURSOR_IBEAM 761 ? SDL_SYSTEM_CURSOR_IBEAM
762 : SDL_SYSTEM_CURSOR_ARROW); 762 : SDL_SYSTEM_CURSOR_ARROW);
763 } 763 }
diff --git a/src/ui/keys.c b/src/ui/keys.c
index 456ca928..6ad9d360 100644
--- a/src/ui/keys.c
+++ b/src/ui/keys.c
@@ -438,7 +438,7 @@ iBool processEvent_Keys(const SDL_Event *ev) {
438 postCommandf_App("%s repeat:1", cstr_String(&bind->command)); 438 postCommandf_App("%s repeat:1", cstr_String(&bind->command));
439 } 439 }
440 else { 440 else {
441 postCommandString_App(&bind->command); 441 postCommandString_Root(NULL, &bind->command);
442 } 442 }
443 return iTrue; 443 return iTrue;
444 } 444 }
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c
index d8e594de..e0de97bf 100644
--- a/src/ui/lookupwidget.c
+++ b/src/ui/lookupwidget.c
@@ -654,11 +654,14 @@ 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(w->root); 657 iRoot *root = w->root;
658 const iRect navBarBounds = bounds_Widget(findWidget_App("navbar")); 658 const iInt2 rootSize = size_Root(root);
659 setFixedSize_Widget(w, init_I2(width_Widget(findWidget_App("url")), 659 const iRect navBarBounds = bounds_Widget(findChild_Widget(root->widget, "navbar"));
660 printf("navbar x:%d w:%d\n", navBarBounds.pos.x, navBarBounds.size.x);
661 iWidget *url = findChild_Widget(root->widget, "url");
662 setFixedSize_Widget(w, init_I2(width_Widget(url),
660 (rootSize.y - bottom_Rect(navBarBounds)) / 2)); 663 (rootSize.y - bottom_Rect(navBarBounds)) / 2));
661 setPos_Widget(w, bottomLeft_Rect(bounds_Widget(findWidget_App("url")))); 664 setPos_Widget(w, windowToLocal_Widget(w, bottomLeft_Rect(bounds_Widget(url))));
662#if defined (iPlatformAppleMobile) 665#if defined (iPlatformAppleMobile)
663 /* Adjust height based on keyboard size. */ { 666 /* Adjust height based on keyboard size. */ {
664 w->rect.size.y = visibleRootSize_Window(window).y - top_Rect(bounds_Widget(w)); 667 w->rect.size.y = visibleRootSize_Window(window).y - top_Rect(bounds_Widget(w));
@@ -667,6 +670,7 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
667 safeAreaInsets_iOS(&l, NULL, &r, NULL); 670 safeAreaInsets_iOS(&l, NULL, &r, NULL);
668 w->rect.size.x = rootSize.x - l - r; 671 w->rect.size.x = rootSize.x - l - r;
669 w->rect.pos.x = l; 672 w->rect.pos.x = l;
673 /* TODO: Need to use windowToLocal_Widget? */
670 } 674 }
671 } 675 }
672#endif 676#endif
@@ -694,7 +698,7 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
694 setText_InputWidget(url, url_DocumentWidget(document_App())); 698 setText_InputWidget(url, url_DocumentWidget(document_App()));
695 showCollapsed_Widget(w, iFalse); 699 showCollapsed_Widget(w, iFalse);
696 setCursor_LookupWidget_(d, iInvalidPos); 700 setCursor_LookupWidget_(d, iInvalidPos);
697 postCommandString_App(&item->command); 701 postCommandString_Root(get_Root(), &item->command);
698 postCommand_App("focus.set id:"); /* unfocus */ 702 postCommand_App("focus.set id:"); /* unfocus */
699 } 703 }
700 return iTrue; 704 return iTrue;
diff --git a/src/ui/root.c b/src/ui/root.c
index f91c3ba5..8faeb215 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -255,6 +255,13 @@ iRoot *get_Root(void) {
255 return activeRoot_; 255 return activeRoot_;
256} 256}
257 257
258iAnyObject *findWidget_Root(const char *id) {
259 if (activeRoot_) {
260 return findChild_Widget(activeRoot_->widget, id);
261 }
262 return NULL;
263}
264
258void destroyPending_Root(iRoot *d) { 265void destroyPending_Root(iRoot *d) {
259 setCurrent_Root(d); 266 setCurrent_Root(d);
260 iForEach(PtrSet, i, d->pendingDestruction) { 267 iForEach(PtrSet, i, d->pendingDestruction) {
@@ -286,7 +293,7 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
286 iWidget *menu = findChild_Widget(button, "menu"); 293 iWidget *menu = findChild_Widget(button, "menu");
287 iAssert(menu); 294 iAssert(menu);
288 if (!isVisible_Widget(menu)) { 295 if (!isVisible_Widget(menu)) {
289 openMenu_Widget(menu, init_I2(0, button->rect.size.y)); 296 openMenu_Widget(menu, bottomLeft_Rect(bounds_Widget(button)));
290 } 297 }
291 else { 298 else {
292 closeMenu_Widget(menu); 299 closeMenu_Widget(menu);
@@ -619,10 +626,10 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
619 iString *newUrl = copy_String(text_InputWidget(url)); 626 iString *newUrl = copy_String(text_InputWidget(url));
620 trim_String(newUrl); 627 trim_String(newUrl);
621 if (willPerformSearchQuery_(newUrl)) { 628 if (willPerformSearchQuery_(newUrl)) {
622 postCommandf_App("open url:%s", cstr_String(searchQueryUrl_App(newUrl))); 629 postCommandf_Root(navBar->root, "open url:%s", cstr_String(searchQueryUrl_App(newUrl)));
623 } 630 }
624 else { 631 else {
625 postCommandf_App( 632 postCommandf_Root(navBar->root,
626 "open url:%s", 633 "open url:%s",
627 cstr_String(absoluteUrl_String(&iStringLiteral(""), collect_String(newUrl)))); 634 cstr_String(absoluteUrl_String(&iStringLiteral(""), collect_String(newUrl))));
628 } 635 }
@@ -677,6 +684,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
677 else if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd)) { 684 else if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd)) {
678 iWidget *widget = pointer_Command(cmd); 685 iWidget *widget = pointer_Command(cmd);
679 iWidget *menu = findWidget_App("doctabs.menu"); 686 iWidget *menu = findWidget_App("doctabs.menu");
687 iAssert(menu->root == navBar->root);
680 if (isTabButton_Widget(widget)) { 688 if (isTabButton_Widget(widget)) {
681 if (!isVisible_Widget(menu)) { 689 if (!isVisible_Widget(menu)) {
682 iWidget *tabs = findWidget_App("doctabs"); 690 iWidget *tabs = findWidget_App("doctabs");
@@ -758,7 +766,7 @@ static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) {
758 argLabel_Command(cmd, "button") == SDL_BUTTON_RIGHT) { 766 argLabel_Command(cmd, "button") == SDL_BUTTON_RIGHT) {
759 iWidget *menu = findChild_Widget(toolBar, "toolbar.menu"); 767 iWidget *menu = findChild_Widget(toolBar, "toolbar.menu");
760 arrange_Widget(menu); 768 arrange_Widget(menu);
761 openMenu_Widget(menu, init_I2(0, -height_Widget(menu))); 769 openMenu_Widget(menu, innerToWindow_Widget(menu, init_I2(0, -height_Widget(menu))));
762 return iTrue; 770 return iTrue;
763 } 771 }
764 else if (equal_Command(cmd, "toolbar.showview")) { 772 else if (equal_Command(cmd, "toolbar.showview")) {
@@ -868,6 +876,7 @@ void updateMetrics_Root(iRoot *d) {
868 876
869void createUserInterface_Root(iRoot *d) { 877void createUserInterface_Root(iRoot *d) {
870 iWidget *root = d->widget = new_Widget(); 878 iWidget *root = d->widget = new_Widget();
879 iAssert(root->root == d);
871 setId_Widget(root, "root"); 880 setId_Widget(root, "root");
872 /* Children of root cover the entire window. */ 881 /* Children of root cover the entire window. */
873 setFlags_Widget( 882 setFlags_Widget(
diff --git a/src/ui/root.h b/src/ui/root.h
index 39e7bf3c..e5fdcbe4 100644
--- a/src/ui/root.h
+++ b/src/ui/root.h
@@ -1,6 +1,7 @@
1#pragma once 1#pragma once
2 2
3#include "widget.h" 3#include "widget.h"
4#include "color.h"
4#include <the_Foundation/ptrset.h> 5#include <the_Foundation/ptrset.h>
5#include <the_Foundation/vec2.h> 6#include <the_Foundation/vec2.h>
6 7
@@ -8,12 +9,10 @@ iDeclareType(Root)
8 9
9struct Impl_Root { 10struct Impl_Root {
10 iWidget * widget; 11 iWidget * widget;
11 iWidget * hover;
12 iWidget * mouseGrab;
13 iWidget * focus;
14 iPtrArray *onTop; /* order is important; last one is topmost */ 12 iPtrArray *onTop; /* order is important; last one is topmost */
15 iPtrSet * pendingDestruction; 13 iPtrSet * pendingDestruction;
16 int loadAnimTimer; 14 int loadAnimTimer;
15 iColor tmPalette[tmMax_ColorId]; /* theme-specific palette */
17}; 16};
18 17
19iDeclareTypeConstruction(Root) 18iDeclareTypeConstruction(Root)
@@ -24,6 +23,7 @@ void createUserInterface_Root (iRoot *);
24 23
25void setCurrent_Root (iRoot *); 24void setCurrent_Root (iRoot *);
26iRoot * get_Root (void); 25iRoot * get_Root (void);
26iAnyObject *findWidget_Root (const char *id); /* under current Root */
27 27
28iPtrArray * onTop_Root (iRoot *); 28iPtrArray * onTop_Root (iRoot *);
29void destroyPending_Root (iRoot *); 29void destroyPending_Root (iRoot *);
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 62d195fb..7d08b83a 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -713,14 +713,14 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, const iSidebarItem *it
713 break; 713 break;
714 } 714 }
715 case feeds_SidebarMode: { 715 case feeds_SidebarMode: {
716 postCommandString_App( 716 postCommandString_Root(get_Root(),
717 feedEntryOpenCommand_String(&item->url, openTabMode_Sym(modState_Keys()))); 717 feedEntryOpenCommand_String(&item->url, openTabMode_Sym(modState_Keys())));
718 break; 718 break;
719 } 719 }
720 case bookmarks_SidebarMode: 720 case bookmarks_SidebarMode:
721 case history_SidebarMode: { 721 case history_SidebarMode: {
722 if (!isEmpty_String(&item->url)) { 722 if (!isEmpty_String(&item->url)) {
723 postCommandf_App("open newtab:%d url:%s", 723 postCommandf_Root(get_Root(), "open newtab:%d url:%s",
724 openTabMode_Sym(modState_Keys()), 724 openTabMode_Sym(modState_Keys()),
725 cstr_String(&item->url)); 725 cstr_String(&item->url));
726 } 726 }
@@ -961,13 +961,13 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
961 } 961 }
962 else if (isCommand_Widget(w, ev, "mouse.moved")) { 962 else if (isCommand_Widget(w, ev, "mouse.moved")) {
963 if (isResizing_SidebarWidget_(d)) { 963 if (isResizing_SidebarWidget_(d)) {
964 const iInt2 local = localCoord_Widget(w, coord_Command(cmd)); 964 const iInt2 inner = windowToInner_Widget(w, coord_Command(cmd));
965 const int resMid = d->resizer->rect.size.x / 2; 965 const int resMid = d->resizer->rect.size.x / 2;
966 setWidth_SidebarWidget( 966 setWidth_SidebarWidget(
967 d, 967 d,
968 ((d->side == left_SideBarSide 968 ((d->side == left_SideBarSide
969 ? local.x 969 ? inner.x
970 : (size_Root(w->root).x - coord_Command(cmd).x)) + 970 : (right_Rect(rect_Root(w->root)) - coord_Command(cmd).x)) +
971 resMid) / (float) gap_UI); 971 resMid) / (float) gap_UI);
972 } 972 }
973 return iTrue; 973 return iTrue;
@@ -1086,7 +1086,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1086 const iSidebarItem *item = d->contextItem; 1086 const iSidebarItem *item = d->contextItem;
1087 if (item) { 1087 if (item) {
1088 if (isCommand_Widget(w, ev, "feed.entry.opentab")) { 1088 if (isCommand_Widget(w, ev, "feed.entry.opentab")) {
1089 postCommandString_App(feedEntryOpenCommand_String(&item->url, 1)); 1089 postCommandString_Root(get_Root(), feedEntryOpenCommand_String(&item->url, 1));
1090 return iTrue; 1090 return iTrue;
1091 } 1091 }
1092 if (isCommand_Widget(w, ev, "feed.entry.toggleread")) { 1092 if (isCommand_Widget(w, ev, "feed.entry.toggleread")) {
diff --git a/src/ui/touch.c b/src/ui/touch.c
index e49fce2a..b2c52526 100644
--- a/src/ui/touch.c
+++ b/src/ui/touch.c
@@ -233,7 +233,8 @@ static void dispatchNotification_Touch_(const iTouch *d, int code) {
233 .type = SDL_USEREVENT, 233 .type = SDL_USEREVENT,
234 .timestamp = SDL_GetTicks(), 234 .timestamp = SDL_GetTicks(),
235 .code = code, 235 .code = code,
236 .data1 = d->affinity 236 .data1 = d->affinity,
237 .data2 = d->affinity->root
237 }); 238 });
238 setCurrent_Root(oldRoot); 239 setCurrent_Root(oldRoot);
239 } 240 }
diff --git a/src/ui/util.c b/src/ui/util.c
index edfd43cc..b487e13d 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -733,12 +733,13 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
733 return menu; 733 return menu;
734} 734}
735 735
736void openMenu_Widget(iWidget *d, iInt2 coord) { 736void openMenu_Widget(iWidget *d, iInt2 windowCoord) {
737 openMenuFlags_Widget(d, coord, iTrue); 737 openMenuFlags_Widget(d, windowCoord, iTrue);
738} 738}
739 739
740void openMenuFlags_Widget(iWidget *d, iInt2 coord, iBool postCommands) { 740void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
741 const iInt2 rootSize = size_Window(get_Window()); 741 const iRect rootRect = rect_Root(d->root);
742 const iInt2 rootSize = rootRect.size;
742 const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); 743 const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App());
743 const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0; 744 const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0;
744 if (postCommands) { 745 if (postCommands) {
@@ -756,7 +757,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 coord, iBool postCommands) {
756 if (!isSlidePanel) { 757 if (!isSlidePanel) {
757 setFlags_Widget(d, borderTop_WidgetFlag, iTrue); 758 setFlags_Widget(d, borderTop_WidgetFlag, iTrue);
758 } 759 }
759 d->rect.size.x = size_Window(get_Window()).x; 760 d->rect.size.x = rootSize.x;
760 } 761 }
761 /* Update item fonts. */ { 762 /* Update item fonts. */ {
762 iForEach(ObjectList, i, children_Widget(d)) { 763 iForEach(ObjectList, i, children_Widget(d)) {
@@ -790,14 +791,14 @@ void openMenuFlags_Widget(iWidget *d, iInt2 coord, iBool postCommands) {
790 } 791 }
791 } 792 }
792 else { 793 else {
793 d->rect.pos = coord; 794 d->rect.pos = windowToLocal_Widget(d, windowCoord);
794 } 795 }
795 /* Ensure the full menu is visible. */ 796 /* Ensure the full menu is visible. */
796 const iRect bounds = bounds_Widget(d); 797 const iRect bounds = bounds_Widget(d);
797 int leftExcess = -left_Rect(bounds); 798 int leftExcess = left_Rect(rootRect) - left_Rect(bounds);
798 int rightExcess = right_Rect(bounds) - rootSize.x; 799 int rightExcess = right_Rect(bounds) - right_Rect(rootRect);
799 int topExcess = -top_Rect(bounds); 800 int topExcess = top_Rect(rootRect) - top_Rect(bounds);
800 int bottomExcess = bottom_Rect(bounds) - rootSize.y; 801 int bottomExcess = bottom_Rect(bounds) - bottom_Rect(rootRect);
801#if defined (iPlatformAppleMobile) 802#if defined (iPlatformAppleMobile)
802 /* Reserve space for the system status bar. */ { 803 /* Reserve space for the system status bar. */ {
803 float l, t, r, b; 804 float l, t, r, b;
@@ -868,7 +869,7 @@ int checkContextMenu_Widget(iWidget *menu, const SDL_Event *ev) {
868 } 869 }
869 const iInt2 mousePos = init_I2(ev->button.x, ev->button.y); 870 const iInt2 mousePos = init_I2(ev->button.x, ev->button.y);
870 if (contains_Widget(menu->parent, mousePos)) { 871 if (contains_Widget(menu->parent, mousePos)) {
871 openMenu_Widget(menu, localCoord_Widget(menu->parent, mousePos)); 872 openMenu_Widget(menu, mousePos);
872 return 0x2; 873 return 0x2;
873 } 874 }
874 } 875 }
@@ -1044,7 +1045,7 @@ void showTabPage_Widget(iWidget *tabs, const iWidget *page) {
1044 } 1045 }
1045 /* Notify. */ 1046 /* Notify. */
1046 if (!isEmpty_String(id_Widget(page))) { 1047 if (!isEmpty_String(id_Widget(page))) {
1047 postCommandf_App("tabs.changed id:%s", cstr_String(id_Widget(page))); 1048 postCommandf_Root(page->root, "tabs.changed id:%s", cstr_String(id_Widget(page)));
1048 } 1049 }
1049} 1050}
1050 1051
@@ -1160,7 +1161,7 @@ static iBool slidePanelHandler_(iWidget *d, const char *cmd) {
1160 if (equal_Command(cmd, "panel.open")) { 1161 if (equal_Command(cmd, "panel.open")) {
1161 iWidget *button = pointer_Command(cmd); 1162 iWidget *button = pointer_Command(cmd);
1162 iWidget *panel = userData_Object(button); 1163 iWidget *panel = userData_Object(button);
1163 openMenu_Widget(panel, zero_I2()); 1164 openMenu_Widget(panel, innerToWindow_Widget(panel, zero_I2()));
1164 setFlags_Widget(panel, disabled_WidgetFlag, iFalse); 1165 setFlags_Widget(panel, disabled_WidgetFlag, iFalse);
1165// updateTextCStr_LabelWidget(findWidget_App("panel.back"), ); 1166// updateTextCStr_LabelWidget(findWidget_App("panel.back"), );
1166 return iTrue; 1167 return iTrue;
diff --git a/src/ui/util.h b/src/ui/util.h
index aaffb1b8..b8d10cf0 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -212,8 +212,8 @@ struct Impl_MenuItem {
212}; 212};
213 213
214iWidget * makeMenu_Widget (iWidget *parent, const iMenuItem *items, size_t n); /* returns no ref */ 214iWidget * makeMenu_Widget (iWidget *parent, const iMenuItem *items, size_t n); /* returns no ref */
215void openMenu_Widget (iWidget *, iInt2 coord); 215void openMenu_Widget (iWidget *, iInt2 windowCoord);
216void openMenuFlags_Widget(iWidget *d, iInt2 coord, iBool postCommands); 216void openMenuFlags_Widget(iWidget *, iInt2 windowCoord, iBool postCommands);
217void closeMenu_Widget (iWidget *); 217void closeMenu_Widget (iWidget *);
218 218
219iLabelWidget * findMenuItem_Widget (iWidget *menu, const char *command); 219iLabelWidget * findMenuItem_Widget (iWidget *menu, const char *command);
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 5d924b57..6d6bc202 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -99,7 +99,7 @@ static void aboutToBeDestroyed_Widget_(iWidget *d) {
99 removeOne_PtrArray(onTop_Root(d->root), d); 99 removeOne_PtrArray(onTop_Root(d->root), d);
100 } 100 }
101 if (isHover_Widget(d)) { 101 if (isHover_Widget(d)) {
102 d->root->hover = NULL; 102 get_Window()->hover = NULL;
103 } 103 }
104 iForEach(ObjectList, i, d->children) { 104 iForEach(ObjectList, i, d->children) {
105 aboutToBeDestroyed_Widget_(as_Widget(i.object)); 105 aboutToBeDestroyed_Widget_(as_Widget(i.object));
@@ -675,16 +675,30 @@ static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) {
675 675
676iRect bounds_Widget(const iWidget *d) { 676iRect bounds_Widget(const iWidget *d) {
677 iRect bounds = d->rect; 677 iRect bounds = d->rect;
678 applyVisualOffset_Widget_(d, &bounds.pos); 678 bounds.pos = localToWindow_Widget(d, bounds.pos);
679 return bounds;
680}
681
682iInt2 localToWindow_Widget(const iWidget *d, iInt2 localCoord) {
683 iInt2 window = localCoord;
684 applyVisualOffset_Widget_(d, &window);
679 for (const iWidget *w = d->parent; w; w = w->parent) { 685 for (const iWidget *w = d->parent; w; w = w->parent) {
680 iInt2 pos = w->rect.pos; 686 iInt2 pos = w->rect.pos;
681 applyVisualOffset_Widget_(w, &pos); 687 applyVisualOffset_Widget_(w, &pos);
682 addv_I2(&bounds.pos, pos); 688 addv_I2(&window, pos);
683 } 689 }
684#if defined (iPlatformMobile) 690#if defined (iPlatformMobile)
685 bounds.pos.y += value_Anim(&get_Window()->rootOffset); 691 window.y += value_Anim(&get_Window()->rootOffset);
686#endif 692#endif
687 return bounds; 693 return window;
694}
695
696iInt2 windowToLocal_Widget(const iWidget *d, iInt2 windowCoord) {
697 iInt2 local = windowCoord;
698 for (const iWidget *w = d->parent; w; w = w->parent) {
699 subv_I2(&local, w->rect.pos);
700 }
701 return local;
688} 702}
689 703
690iRect boundsWithoutVisualOffset_Widget(const iWidget *d) { 704iRect boundsWithoutVisualOffset_Widget(const iWidget *d) {
@@ -695,25 +709,32 @@ iRect boundsWithoutVisualOffset_Widget(const iWidget *d) {
695 return bounds; 709 return bounds;
696} 710}
697 711
698iInt2 localCoord_Widget(const iWidget *d, iInt2 coord) { 712iInt2 innerToWindow_Widget(const iWidget *d, iInt2 innerCoord) {
713 for (const iWidget *w = d; w; w = w->parent) {
714 addv_I2(&innerCoord, w->rect.pos);
715 }
716 return innerCoord;
717}
718
719iInt2 windowToInner_Widget(const iWidget *d, iInt2 windowCoord) {
699 for (const iWidget *w = d; w; w = w->parent) { 720 for (const iWidget *w = d; w; w = w->parent) {
700 subv_I2(&coord, w->rect.pos); 721 subv_I2(&windowCoord, w->rect.pos);
701 } 722 }
702 return coord; 723 return windowCoord;
703} 724}
704 725
705iBool contains_Widget(const iWidget *d, iInt2 coord) { 726iBool contains_Widget(const iWidget *d, iInt2 windowCoord) {
706 return containsExpanded_Widget(d, coord, 0); 727 return containsExpanded_Widget(d, windowCoord, 0);
707} 728}
708 729
709iBool containsExpanded_Widget(const iWidget *d, iInt2 coord, int expand) { 730iBool containsExpanded_Widget(const iWidget *d, iInt2 windowCoord, int expand) {
710 const iRect bounds = { 731 const iRect bounds = {
711 zero_I2(), 732 zero_I2(),
712 addY_I2(d->rect.size, 733 addY_I2(d->rect.size,
713 d->flags & drawBackgroundToBottom_WidgetFlag ? size_Root(d->root).y : 0) 734 d->flags & drawBackgroundToBottom_WidgetFlag ? size_Root(d->root).y : 0)
714 }; 735 };
715 return contains_Rect(expand ? expanded_Rect(bounds, init1_I2(expand)) : bounds, 736 return contains_Rect(expand ? expanded_Rect(bounds, init1_I2(expand)) : bounds,
716 localCoord_Widget(d, coord)); 737 windowToInner_Widget(d, windowCoord));
717} 738}
718 739
719iLocalDef iBool isKeyboardEvent_(const SDL_Event *ev) { 740iLocalDef iBool isKeyboardEvent_(const SDL_Event *ev) {
@@ -738,20 +759,15 @@ static iBool filterEvent_Widget_(const iWidget *d, const SDL_Event *ev) {
738} 759}
739 760
740void unhover_Widget(void) { 761void unhover_Widget(void) {
741 /* TODO: Which root? */ 762 get_Window()->hover = NULL;
742 get_Root()->hover = NULL;
743} 763}
744 764
745iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { 765iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
746 iAssert(d->root == get_Root()); 766 iAssert(d->root == get_Root());
747 if (!d->parent) { 767 if (!d->parent) {
748 if (ev->type == SDL_MOUSEMOTION) { 768 if (get_Window()->focus && get_Window()->focus->root == d->root && isKeyboardEvent_(ev)) {
749 /* Hover widget may change. */
750 setHover_Widget(NULL);
751 }
752 if (d->root->focus && isKeyboardEvent_(ev)) {
753 /* Root dispatches keyboard events directly to the focused widget. */ 769 /* Root dispatches keyboard events directly to the focused widget. */
754 if (dispatchEvent_Widget(d->root->focus, ev)) { 770 if (dispatchEvent_Widget(get_Window()->focus, ev)) {
755 return iTrue; 771 return iTrue;
756 } 772 }
757 } 773 }
@@ -780,7 +796,7 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
780 } 796 }
781 } 797 }
782 else if (ev->type == SDL_MOUSEMOTION && 798 else if (ev->type == SDL_MOUSEMOTION &&
783 (!d->root->hover || hasParent_Widget(d, d->root->hover)) && 799 (!get_Window()->hover || hasParent_Widget(d, get_Window()->hover)) &&
784 flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag && 800 flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag &&
785 ~flags_Widget(d) & disabled_WidgetFlag) { 801 ~flags_Widget(d) & disabled_WidgetFlag) {
786 if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) { 802 if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) {
@@ -798,7 +814,8 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
798 handle the events first. */ 814 handle the events first. */
799 iReverseForEach(ObjectList, i, d->children) { 815 iReverseForEach(ObjectList, i, d->children) {
800 iWidget *child = as_Widget(i.object); 816 iWidget *child = as_Widget(i.object);
801 if (child == d->root->focus && isKeyboardEvent_(ev)) { 817 iAssert(child->root == d->root);
818 if (child == get_Window()->focus && isKeyboardEvent_(ev)) {
802 continue; /* Already dispatched. */ 819 continue; /* Already dispatched. */
803 } 820 }
804 if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) { 821 if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) {
@@ -870,7 +887,7 @@ static iBool scrollOverflow_Widget_(iWidget *d, int delta) {
870// else { 887// else {
871// bounds.pos.y = iMax(bounds.pos.y, ); 888// bounds.pos.y = iMax(bounds.pos.y, );
872// } 889// }
873 const iInt2 newPos = localCoord_Widget(d->parent, bounds.pos); 890 const iInt2 newPos = windowToInner_Widget(d->parent, bounds.pos);
874 if (!isEqual_I2(newPos, d->rect.pos)) { 891 if (!isEqual_I2(newPos, d->rect.pos)) {
875 d->rect.pos = newPos; 892 d->rect.pos = newPos;
876 refresh_Widget(d); 893 refresh_Widget(d);
@@ -1087,6 +1104,7 @@ iAny *addChildPosFlags_Widget(iWidget *d, iAnyObject *child, enum iWidgetAddPos
1087 iAssert(child); 1104 iAssert(child);
1088 iAssert(d != child); 1105 iAssert(d != child);
1089 iWidget *widget = as_Widget(child); 1106 iWidget *widget = as_Widget(child);
1107 iAssert(widget->root == d->root);
1090 iAssert(!widget->parent); 1108 iAssert(!widget->parent);
1091 if (!d->children) { 1109 if (!d->children) {
1092 d->children = new_ObjectList(); 1110 d->children = new_ObjectList();
@@ -1268,12 +1286,12 @@ iBool isDisabled_Widget(const iAnyObject *d) {
1268 1286
1269iBool isFocused_Widget(const iAnyObject *d) { 1287iBool isFocused_Widget(const iAnyObject *d) {
1270 iAssert(isInstance_Object(d, &Class_Widget)); 1288 iAssert(isInstance_Object(d, &Class_Widget));
1271 return ((const iWidget *) d)->root->focus == d; 1289 return get_Window()->focus == d;
1272} 1290}
1273 1291
1274iBool isHover_Widget(const iAnyObject *d) { 1292iBool isHover_Widget(const iAnyObject *d) {
1275 iAssert(isInstance_Object(d, &Class_Widget)); 1293 iAssert(isInstance_Object(d, &Class_Widget));
1276 return ((const iWidget *) d)->root->hover == d; 1294 return get_Window()->hover == d;
1277} 1295}
1278 1296
1279iBool isSelected_Widget(const iAnyObject *d) { 1297iBool isSelected_Widget(const iAnyObject *d) {
@@ -1319,15 +1337,14 @@ iBool isAffectedByVisualOffset_Widget(const iWidget *d) {
1319} 1337}
1320 1338
1321void setFocus_Widget(iWidget *d) { 1339void setFocus_Widget(iWidget *d) {
1322 iRoot *root = d ? d->root : get_Root(); 1340 iWindow *win = get_Window();
1323 if (root->focus != d) { 1341 if (win->focus != d) {
1324 if (root->focus) { 1342 if (win->focus) {
1325 iAssert(!contains_PtrSet(root->pendingDestruction, root->focus)); 1343 iAssert(!contains_PtrSet(win->focus->root->pendingDestruction, win->focus));
1326 postCommand_Widget(root->focus, "focus.lost"); 1344 postCommand_Widget(win->focus, "focus.lost");
1327 } 1345 }
1328 root->focus = d; 1346 win->focus = d;
1329 if (d) { 1347 if (d) {
1330 iAssert(root == d->root);
1331 iAssert(flags_Widget(d) & focusable_WidgetFlag); 1348 iAssert(flags_Widget(d) & focusable_WidgetFlag);
1332 postCommand_Widget(d, "focus.gained"); 1349 postCommand_Widget(d, "focus.gained");
1333 } 1350 }
@@ -1335,20 +1352,15 @@ void setFocus_Widget(iWidget *d) {
1335} 1352}
1336 1353
1337iWidget *focus_Widget(void) { 1354iWidget *focus_Widget(void) {
1338 return get_Root()->focus; 1355 return get_Window()->focus;
1339} 1356}
1340 1357
1341void setHover_Widget(iWidget *d) { 1358void setHover_Widget(iWidget *d) {
1342 if (d) { 1359 get_Window()->hover = d;
1343 d->root->hover = d;
1344 }
1345 else {
1346 get_Root()->hover = NULL;
1347 }
1348} 1360}
1349 1361
1350iWidget *hover_Widget(void) { 1362iWidget *hover_Widget(void) {
1351 return get_Root()->hover; 1363 return get_Window()->hover;
1352} 1364}
1353 1365
1354static const iWidget *findFocusable_Widget_(const iWidget *d, const iWidget *startFrom, 1366static const iWidget *findFocusable_Widget_(const iWidget *d, const iWidget *startFrom,
@@ -1404,21 +1416,14 @@ iAny *findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusD
1404} 1416}
1405 1417
1406void setMouseGrab_Widget(iWidget *d) { 1418void setMouseGrab_Widget(iWidget *d) {
1407 iRoot *root = d ? d->root : get_Root(); 1419 if (get_Window()->mouseGrab != d) {
1408 if (root->mouseGrab != d) { 1420 get_Window()->mouseGrab = d;
1409 root->mouseGrab = d;
1410 SDL_CaptureMouse(d != NULL); 1421 SDL_CaptureMouse(d != NULL);
1411 } 1422 }
1412} 1423}
1413 1424
1414iWidget *mouseGrab_Widget(void) { 1425iWidget *mouseGrab_Widget(void) {
1415 iWindow *win = get_Window(); 1426 return get_Window()->mouseGrab;
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;
1422} 1427}
1423 1428
1424void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) { 1429void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) {
@@ -1438,7 +1443,7 @@ void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) {
1438 iAssert(isInstance_Object(d, &Class_Widget)); 1443 iAssert(isInstance_Object(d, &Class_Widget));
1439 appendFormat_String(&str, " ptr:%p", d); 1444 appendFormat_String(&str, " ptr:%p", d);
1440 } 1445 }
1441 postCommandString_App(&str); 1446 postCommandString_Root(((const iWidget *) d)->root, &str);
1442 deinit_String(&str); 1447 deinit_String(&str);
1443} 1448}
1444 1449
diff --git a/src/ui/widget.h b/src/ui/widget.h
index f126d40d..2ef035b6 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -169,16 +169,24 @@ void destroy_Widget (iWidget *); /* widget removed and deleted later
169void destroyPending_Widget (void); 169void destroyPending_Widget (void);
170void releaseChildren_Widget (iWidget *); 170void releaseChildren_Widget (iWidget *);
171 171
172/* Coordinate spaces:
173 - window: 0,0 is at the top left corner of the window
174 - local: 0,0 is at the top left corner of the parent widget
175 - inner: 0,0 is at the top left corner of the widget */
176
172iWidget * root_Widget (const iWidget *); 177iWidget * root_Widget (const iWidget *);
173const iString * id_Widget (const iWidget *); 178const iString * id_Widget (const iWidget *);
174int64_t flags_Widget (const iWidget *); 179int64_t flags_Widget (const iWidget *);
175iRect bounds_Widget (const iWidget *); /* outer bounds */ 180iRect bounds_Widget (const iWidget *); /* outer bounds */
176iRect innerBounds_Widget (const iWidget *); 181iRect innerBounds_Widget (const iWidget *);
177iRect boundsWithoutVisualOffset_Widget(const iWidget *); 182iRect boundsWithoutVisualOffset_Widget(const iWidget *);
178iInt2 localCoord_Widget (const iWidget *, iInt2 coord); 183iInt2 localToWindow_Widget (const iWidget *, iInt2 localCoord);
179iBool contains_Widget (const iWidget *, iInt2 coord); 184iInt2 windowToLocal_Widget (const iWidget *, iInt2 windowCoord);
180iBool containsExpanded_Widget (const iWidget *, iInt2 coord, int expand); 185iInt2 innerToWindow_Widget (const iWidget *, iInt2 innerCoord);
181iAny * hitChild_Widget (const iWidget *, iInt2 coord); 186iInt2 windowToInner_Widget (const iWidget *, iInt2 windowCoord);
187iBool contains_Widget (const iWidget *, iInt2 windowCoord);
188iBool containsExpanded_Widget (const iWidget *, iInt2 windowCoord, int expand);
189iAny * hitChild_Widget (const iWidget *, iInt2 windowCoord);
182iAny * findChild_Widget (const iWidget *, const char *id); 190iAny * findChild_Widget (const iWidget *, const char *id);
183const iPtrArray *findChildren_Widget (const iWidget *, const char *id); 191const iPtrArray *findChildren_Widget (const iWidget *, const char *id);
184iAny * findParentClass_Widget (const iWidget *, const iAnyClass *class); 192iAny * findParentClass_Widget (const iWidget *, const iAnyClass *class);
diff --git a/src/ui/window.c b/src/ui/window.c
index e47b579f..78ea182c 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -189,8 +189,11 @@ static void windowSizeChanged_Window_(iWindow *d) {
189 iRoot *root = d->roots[i]; 189 iRoot *root = d->roots[i];
190 if (root) { 190 if (root) {
191 root->widget->rect.pos = init_I2(rootSize.x * i, 0); 191 root->widget->rect.pos = init_I2(rootSize.x * i, 0);
192 root->widget->rect.size = rootSize; /* Reposition roots. */ 192 root->widget->rect.size = addX_I2(rootSize, -gap_UI / 4);
193 root->widget->minSize = rootSize; 193 if (i == 1) {
194 root->widget->rect.pos.x += gap_UI / 8;
195 }
196 root->widget->minSize = root->widget->rect.size;
194 updatePadding_Root(root); 197 updatePadding_Root(root);
195 arrange_Widget(root->widget); 198 arrange_Widget(root->widget);
196 } 199 }
@@ -220,7 +223,7 @@ static void updateSize_Window_(iWindow *d, iBool notifyAlways) {
220void drawWhileResizing_Window(iWindow *d, int w, int h) { 223void drawWhileResizing_Window(iWindow *d, int w, int h) {
221 /* This is called while a window resize is in progress, so we can be pretty confident 224 /* This is called while a window resize is in progress, so we can be pretty confident
222 the size has actually changed. */ 225 the size has actually changed. */
223 d->size = init_I2(w, h); 226 d->size = coord_Window(d, w, h);
224 windowSizeChanged_Window_(d); 227 windowSizeChanged_Window_(d);
225 draw_Window(d); 228 draw_Window(d);
226} 229}
@@ -274,7 +277,8 @@ static float displayScale_Window_(const iWindow *d) {
274} 277}
275 278
276static void drawBlank_Window_(iWindow *d) { 279static void drawBlank_Window_(iWindow *d) {
277 const iColor bg = get_Color(uiBackground_ColorId); 280// const iColor bg = get_Color(uiBackground_ColorId);
281 const iColor bg = { 128, 128, 128, 255 }; /* TODO: Have no root yet. */
278 SDL_SetRenderDrawColor(d->render, bg.r, bg.g, bg.b, 255); 282 SDL_SetRenderDrawColor(d->render, bg.r, bg.g, bg.b, 255);
279 SDL_RenderClear(d->render); 283 SDL_RenderClear(d->render);
280 SDL_RenderPresent(d->render); 284 SDL_RenderPresent(d->render);
@@ -373,6 +377,9 @@ void init_Window(iWindow *d, iRect rect) {
373 d->win = NULL; 377 d->win = NULL;
374 d->size = zero_I2(); /* will be updated below */ 378 d->size = zero_I2(); /* will be updated below */
375 iZap(d->roots); 379 iZap(d->roots);
380 d->hover = NULL;
381 d->mouseGrab = NULL;
382 d->focus = NULL;
376 iZap(d->cursors); 383 iZap(d->cursors);
377 d->place.initialPos = rect.pos; 384 d->place.initialPos = rect.pos;
378 d->place.normalRect = rect; 385 d->place.normalRect = rect;
@@ -520,7 +527,6 @@ iRoot *findRoot_Window(const iWindow *d, const iWidget *widget) {
520 return d->roots[i]; 527 return d->roots[i];
521 } 528 }
522 } 529 }
523 iAssert(iFalse); /* it must be under some Root */
524 return NULL; 530 return NULL;
525} 531}
526 532
@@ -811,10 +817,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
811 event.button.x = pos.x; 817 event.button.x = pos.x;
812 event.button.y = pos.y; 818 event.button.y = pos.y;
813 } 819 }
814 const iWidget *oldHovers[2] = { 820 const iWidget *oldHover = d->hover;
815 d->roots[0]->hover,
816 d->roots[1]->hover,
817 };
818 iBool wasUsed = iFalse; 821 iBool wasUsed = iFalse;
819 /* Dispatch first to the mouse-grabbed widget. */ 822 /* Dispatch first to the mouse-grabbed widget. */
820// iWidget *widget = d->root.widget; 823// iWidget *widget = d->root.widget;
@@ -866,7 +869,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
866 } 869 }
867 } 870 }
868 } 871 }
869 if (oldHovers[0] != d->roots[0]->hover || oldHovers[1] != d->roots[1]->hover) { 872 if (oldHover != d->hover) {
870 postRefresh_App(); 873 postRefresh_App();
871 } 874 }
872 if (event.type == SDL_MOUSEMOTION) { 875 if (event.type == SDL_MOUSEMOTION) {
@@ -879,9 +882,15 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
879} 882}
880 883
881iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) { 884iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) {
882 /* TODO: Dispatch to input-focused root first. */ 885 if (ev->type == SDL_MOUSEMOTION) {
886 /* Hover widget may change. */
887 setHover_Widget(NULL);
888 }
883 iForIndices(i, d->roots) { 889 iForIndices(i, d->roots) {
884 if (d->roots[i]) { 890 if (d->roots[i]) {
891 if (isCommand_SDLEvent(ev) && ev->user.data2 && ev->user.data2 != d->roots[i]) {
892 continue; /* Not meant for this root. */
893 }
885 setCurrent_Root(d->roots[i]); 894 setCurrent_Root(d->roots[i]);
886 const iBool wasUsed = dispatchEvent_Widget(d->roots[i]->widget, ev); 895 const iBool wasUsed = dispatchEvent_Widget(d->roots[i]->widget, ev);
887 if (wasUsed) { 896 if (wasUsed) {
diff --git a/src/ui/window.h b/src/ui/window.h
index 12c540d3..de0133b1 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -68,6 +68,9 @@ struct Impl_Window {
68 SDL_Renderer *render; 68 SDL_Renderer *render;
69 iInt2 size; 69 iInt2 size;
70 iRoot * roots[2]; /* root widget and UI state; second one is for split mode */ 70 iRoot * roots[2]; /* root widget and UI state; second one is for split mode */
71 iWidget * hover;
72 iWidget * mouseGrab;
73 iWidget * focus;
71 float pixelRatio; /* conversion between points and pixels, e.g., coords, window size */ 74 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 */ 75 float displayScale; /* DPI-based scaling factor of current display, affects uiScale only */
73 float uiScale; 76 float uiScale;