=> 2d6ffdd49f83a017b499d515b2973cecf5d656dd
[1mdiff --git a/src/app.c b/src/app.c[m [1mindex fd000dbd..8638e038 100644[m [1m--- a/src/app.c[m [1m+++ b/src/app.c[m [36m@@ -140,6 +140,8 @@[m [mstatic const char *defaultDownloadDir_App_ = "~/Downloads";[m [m static const int idleThreshold_App_ = 1000; /* ms */[m [m [32m+[m[32mtypedef iWindow iMainOrExtraWindow;[m [32m+[m struct Impl_App {[m iCommandLine args;[m iString * overrideDataPath;[m [36m@@ -151,8 +153,9 @@[m [mstruct Impl_App {[m iGmCerts * certs;[m iVisited * visited;[m iBookmarks * bookmarks;[m [31m- iMainWindow *window; /* currently active MainWindow */[m [32m+[m[32m iMainOrExtraWindow *window; /* currently active MainWindow or extra Window */[m iPtrArray mainWindows;[m [32m+[m[32m iPtrArray extraWindows;[m iPtrArray popupWindows;[m iSortedArray tickers; /* per-frame callbacks, used for animations */[m uint32_t lastTickerTime;[m [36m@@ -711,7 +714,7 @@[m [mstatic iBool loadState_App_(iApp *d) {[m };[m int numWins = 0;[m iMainWindow * win = NULL;[m [31m- iMainWindow * currentWin = d->window;[m [32m+[m[32m iMainWindow * currentWin = as_MainWindow(d->window);[m iArray * currentTabs; /* two per window (per root per window) */[m iBool isFirstTab[2];[m currentTabs = collectNew_Array(sizeof(iCurrentTabs));[m [36m@@ -729,7 +732,7 @@[m [mstatic iBool loadState_App_(iApp *d) {[m win = d->window;[m #else[m if (numWins == 1) {[m [31m- win = d->window;[m [32m+[m[32m win = as_MainWindow(d->window);[m }[m else {[m win = new_MainWindow(initialWindowRect_App_(d, numWins - 1));[m [36m@@ -870,7 +873,7 @@[m [mstatic void saveState_App_(const iApp *d) {[m writeData_File(f, magicWindow_App_, 4);[m writeU32_File(f, win->splitMode);[m writeU32_File(f, (win->base.keyRoot == win->base.roots[0] ? 0 : 1) |[m [31m- (win == d->window ? current_WindowStateFlag : 0));[m [32m+[m[32m (constAs_Window(win) == d->window ? current_WindowStateFlag : 0));[m }[m /* State of UI elements. */ {[m iForIndices(i, win->base.roots) {[m [36m@@ -1305,9 +1308,10 @@[m [mstatic void init_App_(iApp *d, int argc, char **argv) {[m }[m }[m init_PtrArray(&d->mainWindows);[m [32m+[m[32m init_PtrArray(&d->extraWindows);[m init_PtrArray(&d->popupWindows);[m [31m- d->window = new_MainWindow(*winRect0); /* first window is always created */[m [31m- addWindow_App(d->window);[m [32m+[m[32m d->window = (iWindow *) new_MainWindow(*winRect0); /* first window is always created */[m [32m+[m[32m addWindow_App(as_MainWindow(d->window));[m load_Visited(d->visited, dataDir_App_());[m load_Bookmarks(d->bookmarks, dataDir_App_());[m load_MimeHooks(d->mimehooks, dataDir_App_());[m [36m@@ -1378,9 +1382,9 @@[m [mstatic void init_App_(iApp *d, int argc, char **argv) {[m iRelease(openCmds);[m }[m fetchRemote_Bookmarks(d->bookmarks);[m [31m- if (deviceType_App() != desktop_AppDeviceType) {[m [32m+[m[32m if (deviceType_App() != desktop_AppDeviceType && d->window == main_WindowType) {[m /* HACK: Force a resize so widgets update their state. */[m [31m- resize_MainWindow(d->window, -1, -1);[m [32m+[m[32m resize_MainWindow(as_MainWindow(d->window), -1, -1);[m }[m #if defined (iPlatformAndroid)[m /* See if there is something to import from backup. */[m [36m@@ -1394,6 +1398,11 @@[m [mstatic void deinit_App(iApp *d) {[m }[m iAssert(isEmpty_PtrArray(&d->popupWindows));[m deinit_PtrArray(&d->popupWindows);[m [32m+[m[32m iReverseForEach(PtrArray, k, &d->extraWindows) {[m [32m+[m[32m delete_Window(k.ptr);[m [32m+[m[32m }[m [32m+[m[32m iAssert(isEmpty_PtrArray(&d->extraWindows));[m [32m+[m[32m deinit_PtrArray(&d->extraWindows);[m #if defined (LAGRANGE_ENABLE_IDLE_SLEEP)[m SDL_RemoveTimer(d->sleepTimer);[m #endif[m [36m@@ -1681,7 +1690,8 @@[m [mstatic iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *ev[m if (eventMode == waitForNewEvents_AppEventMode && isWaitingAllowed_App_(d)) {[m /* We may be allowed to block here until an event comes in. */[m if (isWaitingAllowed_App_(d)) {[m [31m- if (isAppleDesktop_Platform() && d->window && d->window->enableBackBuf) {[m [32m+[m[32m if (isAppleDesktop_Platform() && d->window && d->window->type == main_WindowType &&[m [32m+[m[32m as_MainWindow(d->window)->enableBackBuf) {[m /* SDL Metal workaround: if we block here for too long, there will be a longer[m ~100ms stutter later on after refresh resumes, when the render pipeline does[m some kind of an update (?). */[m [36m@@ -1703,15 +1713,27 @@[m [mstatic iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *ev[m [m static iPtrArray *listWindows_App_(const iApp *d, iPtrArray *windows) {[m clear_PtrArray(windows);[m [31m- iReverseConstForEach(PtrArray, i, &d->popupWindows) {[m [31m- pushBack_PtrArray(windows, i.ptr);[m [32m+[m[32m /* Popups. */ {[m [32m+[m[32m iReverseConstForEach(PtrArray, i, &d->popupWindows) {[m [32m+[m[32m pushBack_PtrArray(windows, i.ptr);[m [32m+[m[32m }[m }[m [32m+[m[32m /* Current active main window. */[m if (d->window) {[m pushBack_PtrArray(windows, d->window);[m }[m [31m- iConstForEach(PtrArray, j, &d->mainWindows) {[m [31m- if (j.ptr != d->window) {[m [31m- pushBack_PtrArray(windows, j.ptr);[m [32m+[m[32m /* Other extra windows. */ {[m [32m+[m[32m iReverseConstForEach(PtrArray, i, &d->extraWindows) {[m [32m+[m[32m if (i.ptr != d->window) {[m [32m+[m[32m pushBack_PtrArray(windows, i.ptr);[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m /* Other main windows. */ {[m [32m+[m[32m iConstForEach(PtrArray, i, &d->mainWindows) {[m [32m+[m[32m if (i.ptr != d->window) {[m [32m+[m[32m pushBack_PtrArray(windows, i.ptr);[m [32m+[m[32m }[m }[m }[m return windows;[m [36m@@ -1762,7 +1784,7 @@[m [mvoid processEvents_App(enum iAppEventMode eventMode) {[m #endif[m postRefresh_App();[m break;[m [31m- case SDL_APP_WILLENTERBACKGROUND:[m [32m+[m[32m case SDL_APP_WILLENTERBACKGROUND: {[m #if defined (iPlatformAppleMobile)[m updateNowPlayingInfo_iOS();[m #endif[m [36m@@ -1771,18 +1793,22 @@[m [mvoid processEvents_App(enum iAppEventMode eventMode) {[m saveIdentities_GmCerts(certs_App());[m save_Bookmarks(d->bookmarks, dataDir_App_());[m #endif[m [31m- setFreezeDraw_MainWindow(d->window, iTrue);[m [32m+[m[32m iForEach(PtrArray, i, &d->mainWindows) {[m [32m+[m[32m setFreezeDraw_MainWindow(*i.value, iTrue);[m [32m+[m[32m }[m savePrefs_App_(d);[m saveState_App_(d);[m d->isSuspended = iTrue;[m break;[m [31m- case SDL_APP_TERMINATING:[m [31m- if (d->window) {[m [31m- setFreezeDraw_MainWindow(d->window, iTrue);[m [32m+[m[32m }[m [32m+[m[32m case SDL_APP_TERMINATING: {[m [32m+[m[32m iForEach(PtrArray, i, &d->mainWindows) {[m [32m+[m[32m setFreezeDraw_MainWindow(*i.value, iTrue);[m }[m savePrefs_App_(d);[m saveState_App_(d);[m break;[m [32m+[m[32m }[m case SDL_DROPFILE: {[m if (!d->window) {[m /* Need to open an empty window now. */[m [36m@@ -1952,7 +1978,8 @@[m [mvoid processEvents_App(enum iAppEventMode eventMode) {[m }[m }[m /* Per-window processing. */[m [31m- if (!wasUsed && !isEmpty_PtrArray(&d->mainWindows)) {[m [32m+[m[32m if (!wasUsed && (!isEmpty_PtrArray(&d->mainWindows) ||[m [32m+[m[32m !isEmpty_PtrArray(&d->extraWindows))) {[m listWindows_App_(d, &windows);[m iConstForEach(PtrArray, iter, &windows) {[m iWindow *window = iter.ptr;[m [36m@@ -2079,11 +2106,18 @@[m [mstatic void runTickers_App_(iApp *d) {[m d->lastTickerTime = 0;[m return;[m }[m [31m- iForIndices(i, d->window->base.roots) {[m [31m- iRoot *root = d->window->base.roots[i];[m [31m- if (root) {[m [31m- root->didAnimateVisualOffsets = iFalse;[m [32m+[m[32m /* Update window state. */ {[m [32m+[m[32m iPtrArray *winList = listWindows_App();[m [32m+[m[32m iForEach(PtrArray, i, winList) {[m [32m+[m[32m iWindow *win = *i.value;[m [32m+[m[32m iForIndices(i, win->roots) {[m [32m+[m[32m iRoot *root = win->roots[i];[m [32m+[m[32m if (root) {[m [32m+[m[32m root->didAnimateVisualOffsets = iFalse;[m [32m+[m[32m }[m [32m+[m[32m }[m }[m [32m+[m[32m delete_PtrArray(winList);[m }[m /* Tickers may add themselves again, so we'll run off a copy. */[m iSortedArray *pending = copy_SortedArray(&d->tickers);[m [36m@@ -2119,10 +2153,10 @@[m [mstatic int resizeWatcher_(void *user, SDL_Event *event) {[m }[m #endif[m /* Find the window that is being resized and redraw it immediately. */[m [31m- iConstForEach(PtrArray, i, &d->mainWindows) {[m [31m- const iMainWindow *win = i.ptr;[m [32m+[m[32m iForEach(PtrArray, i, &d->mainWindows) {[m [32m+[m[32m iMainWindow *win = i.ptr;[m if (SDL_GetWindowID(win->base.win) == winev->windowID) {[m [31m- drawWhileResizing_MainWindow(d->window, winev->data1, winev->data2);[m [32m+[m[32m drawWhileResizing_MainWindow(win, winev->data1, winev->data2);[m break;[m }[m }[m [36m@@ -2132,9 +2166,9 @@[m [mstatic int resizeWatcher_(void *user, SDL_Event *event) {[m [m static int run_App_(iApp *d) {[m /* Initial arrangement. */[m [31m- iForIndices(i, d->window->base.roots) {[m [31m- if (d->window->base.roots[i]) {[m [31m- arrange_Widget(d->window->base.roots[i]->widget);[m [32m+[m[32m iForIndices(i, d->window->roots) {[m [32m+[m[32m if (d->window->roots[i]) {[m [32m+[m[32m arrange_Widget(d->window->roots[i]->widget);[m }[m }[m d->isRunning = iTrue;[m [36m@@ -2147,7 +2181,9 @@[m [mstatic int run_App_(iApp *d) {[m runTickers_App_(d);[m refresh_App();[m /* Change the widget tree while we are not iterating through it. */[m [31m- checkPendingSplit_MainWindow(d->window);[m [32m+[m[32m if (d->window && d->window->type == main_WindowType) {[m [32m+[m[32m checkPendingSplit_MainWindow(as_MainWindow(d->window));[m [32m+[m[32m }[m recycle_Garbage();[m }[m SDL_DelEventWatch(resizeWatcher_, d);[m [36m@@ -2175,6 +2211,7 @@[m [mvoid refresh_App(void) {[m }[m }[m }[m [32m+[m[32m listWindows_App_(d, &windows); /* maybe some windows were deleted, too */[m }[m /* TODO: `pendingRefresh` should be window-specific. */[m if (d->warmupFrames || exchange_Atomic(&d->pendingRefresh, iFalse)) {[m [36m@@ -2473,9 +2510,16 @@[m [mconst iPtrArray *mainWindows_App(void) {[m return &app_.mainWindows;[m }[m [m [31m-void setActiveWindow_App(iMainWindow *win) {[m [32m+[m[32mvoid setActiveWindow_App(iAnyWindow *mainOrExtraWin) {[m iApp *d = &app_;[m [31m- d->window = win;[m [32m+[m[32m d->window = mainOrExtraWin;[m [32m+[m[32m if (d->window) {[m [32m+[m[32m iAssert(d->window->type == main_WindowType || d->window->type == extra_WindowType);[m [32m+[m[32m }[m [32m+[m[32m}[m [32m+[m [32m+[m[32miWindow *activeWindow_App(void) {[m [32m+[m[32m return app_.window;[m }[m [m void addPopup_App(iWindow *popup) {[m [36m@@ -2488,6 +2532,16 @@[m [mvoid removePopup_App(iWindow *popup) {[m removeOne_PtrArray(&d->popupWindows, popup);[m }[m [m [32m+[m[32mvoid addExtraWindow_App(iWindow *extra) {[m [32m+[m[32m iApp *d = &app_;[m [32m+[m[32m pushBack_PtrArray(&d->extraWindows, extra);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mvoid removeExtraWindow_App(iWindow *extra) {[m [32m+[m[32m iApp *d = &app_;[m [32m+[m[32m removeOne_PtrArray(&d->extraWindows, extra);[m [32m+[m[32m}[m [32m+[m iMimeHooks *mimeHooks_App(void) {[m return app_.mimehooks;[m }[m [36m@@ -2638,7 +2692,7 @@[m [mstatic iBool handlePrefsCommands_(iWidget *d, const char *cmd) {[m postCommandf_App("prefs.dialogtab arg:%u",[m tabPageIndex_Widget(tabs, currentTabPage_Widget(tabs)));[m }[m [31m- destroy_Widget(d);[m [32m+[m[32m destroyDialog_Widget(d);[m postCommand_App("prefs.changed");[m return iTrue;[m }[m [36m@@ -2728,7 +2782,23 @@[m [miDocumentWidget *document_Root(iRoot *d) {[m }[m [m iDocumentWidget *document_App(void) {[m [31m- return document_Root(get_Root());[m [32m+[m[32m iDocumentWidget *doc = document_Root(get_Root());[m [32m+[m[32m if (doc) {[m [32m+[m[32m return doc;[m [32m+[m[32m }[m [32m+[m[32m /* Try another window. */[m [32m+[m[32m iConstForEach(PtrArray, i, mainWindows_App()) {[m [32m+[m[32m iWindow *win = *i.value;[m [32m+[m[32m iForIndices(j, win->roots) {[m [32m+[m[32m if (win->roots[j]) {[m [32m+[m[32m doc = document_Root(win->roots[j]);[m [32m+[m[32m if (doc) {[m [32m+[m[32m return doc;[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m return NULL;[m }[m [m iDocumentWidget *document_Command(const char *cmd) {[m [36m@@ -2772,32 +2842,44 @@[m [miDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe[m return doc;[m }[m [m [31m-void closeWindow_App(iMainWindow *win) {[m [32m+[m[32mvoid closeWindow_App(iWindow *win) {[m iApp *d = &app_;[m [31m- iForIndices(r, win->base.roots) {[m [31m- if (win->base.roots[r]) {[m [31m- setTreeFlags_Widget(win->base.roots[r]->widget, destroyPending_WidgetFlag, iTrue);[m [32m+[m[32m iAssert(win->type == main_WindowType || win->type == extra_WindowType);[m [32m+[m[32m const iBool isMain = (win->type == main_WindowType);[m [32m+[m[32m iWindow *activeWindow = d->window;[m [32m+[m[32m iForIndices(r, win->roots) {[m [32m+[m[32m if (win->roots[r]) {[m [32m+[m[32m setTreeFlags_Widget(win->roots[r]->widget, destroyPending_WidgetFlag, iTrue);[m }[m }[m [31m- collect_Garbage(win, (iDeleteFunc) delete_MainWindow);[m [32m+[m[32m collect_Garbage(win, isMain ? (iDeleteFunc) delete_MainWindow[m [32m+[m[32m : (iDeleteFunc) delete_Window);[m postRefresh_App();[m [31m- if (isAppleDesktop_Platform() && size_PtrArray(&d->mainWindows) == 1) {[m [32m+[m[32m if (isAppleDesktop_Platform() && isMain && size_PtrArray(&d->mainWindows) == 1) {[m /* The one and only window is being closed. On macOS, the app will keep running, which[m means we must save the state of the window now or otherwise it will be lost. A newly[m opened window will use this saved state if it's the only window of the app. */[m saveState_App_(d); [m }[m [31m- if (d->window == win) {[m [32m+[m[32m if (activeWindow == win) {[m [32m+[m[32m d->window = NULL;[m /* Activate another window. */[m iForEach(PtrArray, i, &d->mainWindows) {[m [31m- if (i.ptr != d->window) {[m [32m+[m[32m if (i.ptr != activeWindow) {[m SDL_RaiseWindow(i.ptr);[m setActiveWindow_App(i.ptr);[m setCurrent_Window(i.ptr);[m [31m- break;[m }[m }[m }[m [32m+[m[32m if (!d->window) {[m [32m+[m[32m iForEach(PtrArray, j, &d->extraWindows) {[m [32m+[m[32m iWindow *win = j.ptr;[m [32m+[m[32m SDL_RaiseWindow(win->win);[m [32m+[m[32m setActiveWindow_App(win);[m [32m+[m[32m setCurrent_Window(win);[m [32m+[m[32m }[m [32m+[m[32m }[m }[m [m static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {[m [36m@@ -2982,7 +3064,8 @@[m [mstatic const iString *popClosedTabUrl_App_(iApp *d) {[m }[m [m static iBool handleNonWindowRelatedCommand_App_(iApp *d, const char *cmd) {[m [31m- const iBool isFrozen = !d->window || d->window->isDrawFrozen;[m [32m+[m[32m const iBool isFrozen = !d->window ||[m [32m+[m[32m (d->window->type == main_WindowType && as_MainWindow(d->window)->isDrawFrozen);[m /* Commands related to preferences. */[m if (equal_Command(cmd, "prefs.changed")) {[m savePrefs_App_(d);[m [36m@@ -3587,8 +3670,8 @@[m [mstatic iBool handleNonWindowRelatedCommand_App_(iApp *d, const char *cmd) {[m }[m else if (equal_Command(cmd, "ipc.signal")) {[m if (argLabel_Command(cmd, "raise")) {[m [31m- if (d->window && d->window->base.win) {[m [31m- SDL_RaiseWindow(d->window->base.win);[m [32m+[m[32m if (d->window && d->window->win) {[m [32m+[m[32m SDL_RaiseWindow(d->window->win);[m }[m }[m signal_Ipc(arg_Command(cmd));[m [36m@@ -3787,8 +3870,9 @@[m [mstatic iBool handleOpenCommand_App_(iApp *d, const char *cmd) {[m [m iBool handleCommand_App(const char *cmd) {[m iApp *d = &app_;[m [31m- const iBool isFrozen = !d->window || d->window->isDrawFrozen;[m [32m+[m[32m const iBool isFrozen = isDrawFrozen_Window(d->window);[m const iBool isHeadless = numWindows_App() == 0;[m [32m+[m[32m const iBool isMainWin = d->window && d->window->type == main_WindowType;[m if (handleNonWindowRelatedCommand_App_(d, cmd)) {[m return iTrue;[m }[m [36m@@ -3804,25 +3888,26 @@[m [miBool handleCommand_App(const char *cmd) {[m suffixPtr_Command(cmd, "where")));[m return iTrue;[m }[m [31m- else if (equal_Command(cmd, "ui.split")) {[m [32m+[m[32m else if (equal_Command(cmd, "ui.split") && isMainWin) {[m if (argLabel_Command(cmd, "swap")) {[m [31m- swapRoots_MainWindow(d->window);[m [32m+[m[32m swapRoots_MainWindow(as_MainWindow(d->window));[m return iTrue;[m }[m if (argLabel_Command(cmd, "focusother")) {[m [31m- iWindow *baseWin = &d->window->base;[m [32m+[m[32m iWindow *baseWin = d->window;[m if (baseWin->roots[1]) {[m baseWin->keyRoot =[m (baseWin->keyRoot == baseWin->roots[1] ? baseWin->roots[0] : baseWin->roots[1]);[m }[m }[m [31m- d->window->pendingSplitMode =[m [32m+[m[32m iMainWindow *mw = as_MainWindow(d->window);[m [32m+[m[32m mw->pendingSplitMode =[m (argLabel_Command(cmd, "axis") ? vertical_WindowSplit : 0) | (arg_Command(cmd) << 1);[m const char *url = suffixPtr_Command(cmd, "url");[m [31m- setCStr_String(d->window->pendingSplitUrl, url ? url : "");[m [31m- setRange_String(d->window->pendingSplitSetIdent, range_Command(cmd, "setident"));[m [32m+[m[32m setCStr_String(mw->pendingSplitUrl, url ? url : "");[m [32m+[m[32m setRange_String(mw->pendingSplitSetIdent, range_Command(cmd, "setident"));[m if (hasLabel_Command(cmd, "origin")) {[m [31m- set_String(d->window->pendingSplitOrigin, string_Command(cmd, "origin"));[m [32m+[m[32m set_String(mw->pendingSplitOrigin, string_Command(cmd, "origin"));[m }[m postRefresh_App();[m return iTrue;[m [36m@@ -3841,9 +3926,9 @@[m [miBool handleCommand_App(const char *cmd) {[m }[m return iTrue;[m }[m [31m- else if (equal_Command(cmd, "window.fullscreen")) {[m [31m- const iBool wasFull = snap_MainWindow(d->window) == fullscreen_WindowSnap;[m [31m- setSnap_MainWindow(d->window, wasFull ? 0 : fullscreen_WindowSnap);[m [32m+[m[32m else if (equal_Command(cmd, "window.fullscreen") && isMainWin) {[m [32m+[m[32m const iBool wasFull = snap_MainWindow(as_MainWindow(d->window)) == fullscreen_WindowSnap;[m [32m+[m[32m setSnap_MainWindow(as_MainWindow(d->window), wasFull ? 0 : fullscreen_WindowSnap);[m postCommandf_App("window.fullscreen.changed arg:%d", !wasFull);[m return iTrue;[m }[m [36m@@ -4094,7 +4179,7 @@[m [miBool handleCommand_App(const char *cmd) {[m }[m else if (equal_Command(cmd, "keyroot.next")) {[m if (setKeyRoot_Window(as_Window(d->window),[m [31m- otherRoot_Window(as_Window(d->window), d->window->base.keyRoot))) {[m [32m+[m[32m otherRoot_Window(as_Window(d->window), d->window->keyRoot))) {[m setFocus_Widget(NULL);[m }[m return iTrue;[m [36m@@ -4216,6 +4301,10 @@[m [miBool handleCommand_App(const char *cmd) {[m iWidget *button = findUserData_Widget(findChild_Widget(dlg, "panel.top"), idPanel);[m postCommand_Widget(button, "panel.open");[m }[m [32m+[m[32m if (deviceType_App() == desktop_AppDeviceType) {[m [32m+[m[32m /* Detach into a window if it doesn't fit otherwise. */[m [32m+[m[32m promoteDialogToWindow_Widget(dlg);[m [32m+[m[32m }[m }[m else if (equal_Command(cmd, "navigate.home")) {[m /* Look for bookmarks tagged "homepage". */[m [36m@@ -4309,6 +4398,9 @@[m [miBool handleCommand_App(const char *cmd) {[m else if (startsWith_CStr(cmd, "feeds.update.")) {[m const iWidget *navBar = findChild_Widget(get_Window()->roots[0]->widget, "navbar");[m iAnyObject *prog = findChild_Widget(navBar, "feeds.progress");[m [32m+[m[32m if (!navBar || !prog) {[m [32m+[m[32m return iFalse;[m [32m+[m[32m }[m if (equal_Command(cmd, "feeds.update.started") ||[m equal_Command(cmd, "feeds.update.progress")) {[m const int num = arg_Command(cmd);[m [36m@@ -4334,7 +4426,7 @@[m [miBool handleCommand_App(const char *cmd) {[m /* Set of open tabs has changed. */[m postCommand_App("document.openurls.changed");[m if (deviceType_App() == phone_AppDeviceType) {[m [31m- showToolbar_Root(d->window->base.roots[0], iTrue);[m [32m+[m[32m showToolbar_Root(d->window->roots[0], iTrue);[m }[m return iFalse;[m }[m [36m@@ -4578,20 +4670,21 @@[m [miStringSet *listOpenURLs_App(void) {[m }[m [m iMainWindow *mainWindow_App(void) {[m [31m- return app_.window;[m [32m+[m[32m iApp *d = &app_;[m [32m+[m[32m if (d->window && d->window->type == main_WindowType) {[m [32m+[m[32m return as_MainWindow(d->window);[m [32m+[m[32m }[m [32m+[m[32m return NULL;[m }[m [m void closePopups_App(iBool doForce) {[m [32m+[m[32m iUnused(doForce); /* not needed any more? */[m iApp *d = &app_;[m const uint32_t now = SDL_GetTicks();[m iForEach(PtrArray, i, &d->popupWindows) {[m iWindow *win = i.ptr;[m [31m-// if (doForce) {[m [31m-// collect_Garbage(win, (iDeleteFunc) delete_Window);[m [31m-// }[m [31m-// else[m [31m- if (now - win->focusGainedAt > 200) {[m [31m- postCommand_Root(((const iWindow *) i.ptr)->roots[0], "cancel");[m [32m+[m[32m if (now - win->focusGainedAt > 200) {[m [32m+[m[32m postCommand_Root(win->roots[0], "cancel");[m }[m }[m }[m [1mdiff --git a/src/app.h b/src/app.h[m [1mindex ce59bb87..0a35a72b 100644[m [1m--- a/src/app.h[m [1m+++ b/src/app.h[m [36m@@ -41,6 +41,8 @@[m [miDeclareType(Root)[m iDeclareType(Visited)[m iDeclareType(Window)[m [m [32m+[m[32mtypedef void iAnyWindow;[m [32m+[m /* Command line options strings. */[m #define dump_CommandLineOption "dump;d"[m #define dumpIdentity_CommandLineOption "dump-identity;I"[m [36m@@ -134,13 +136,17 @@[m [mvoid removeTicker_App (iTickerFunc ticker, iAny *context);[m [m void addWindow_App (iMainWindow *win);[m void removeWindow_App (iMainWindow *win);[m [31m-void setActiveWindow_App (iMainWindow *win);[m [31m-void closeWindow_App (iMainWindow *win);[m [32m+[m[32mvoid setActiveWindow_App (iAnyWindow *mainOrExtraWin);[m [32m+[m[32miWindow * activeWindow_App (void);[m [32m+[m[32mvoid closeWindow_App (iWindow *win); /* main or extra window */[m size_t numWindows_App (void);[m size_t windowIndex_App (const iMainWindow *win);[m iMainWindow *newMainWindow_App (void);[m const iPtrArray *mainWindows_App(void);[m iMainWindow * mainWindow_App (void); /* currently active main window */[m [32m+[m [32m+[m[32mvoid addExtraWindow_App (iWindow *extra);[m [32m+[m[32mvoid removeExtraWindow_App(iWindow *extra);[m void addPopup_App (iWindow *popup);[m void removePopup_App (iWindow *popup);[m void closePopups_App (iBool doForce);[m [1mdiff --git a/src/macos.m b/src/macos.m[m [1mindex 14b4cfc0..b3676444 100644[m [1m--- a/src/macos.m[m [1m+++ b/src/macos.m[m [36m@@ -930,7 +930,7 @@[m [mvoid log_MacOS(const char *msg) {[m void showPopupMenu_MacOS(iWidget *source, iInt2 windowCoord, const iMenuItem *items, size_t n) {[m NSMenu * menu = [[NSMenu alloc] init];[m MenuCommands *menuCommands = [[MenuCommands alloc] init];[m [31m- iWindow * window = as_Window(mainWindow_App());[m [32m+[m[32m iWindow * window = activeWindow_App();[m NSWindow * nsWindow = nsWindow_(window->win);[m /* View coordinates are flipped. */[m iBool isCentered = iFalse;[m [1mdiff --git a/src/ui/color.c b/src/ui/color.c[m [1mindex 3f392987..8571c7d7 100644[m [1m--- a/src/ui/color.c[m [1m+++ b/src/ui/color.c[m [36m@@ -358,6 +358,13 @@[m [miColor get_Color(int color) {[m return *rgba;[m }[m [m [32m+[m[32miColor default_Color(int color) {[m [32m+[m[32m if (color >= 0 && color < iElemCount(darkPalette_)) {[m [32m+[m[32m return (isDark_ColorTheme(prefs_App()->theme) ? darkPalette_ : lightPalette_)[color];[m [32m+[m[32m }[m [32m+[m[32m return (iColor){ 0, 0, 0, 0 };[m [32m+[m[32m}[m [32m+[m iColor getMixed_Color(int color1, int color2, float t) {[m return mix_Color(get_Color(color1), get_Color(color2), t);[m }[m [1mdiff --git a/src/ui/color.h b/src/ui/color.h[m [1mindex ea285ca6..14b2d8bb 100644[m [1m--- a/src/ui/color.h[m [1m+++ b/src/ui/color.h[m [36m@@ -254,6 +254,7 @@[m [mvoid set_Color (int color, iColor rgba);[m iColor mix_Color (iColor c1, iColor c2, float t);[m iColor getMixed_Color (int color1, int color2, float t);[m int delta_Color (iColor c1, iColor c2);[m [32m+[m[32miColor default_Color (int color); /* affected by dark/light mode */[m [m iLocalDef iHSLColor get_HSLColor(int color) {[m return hsl_Color(get_Color(color));[m [1mdiff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c[m [1mindex 6da9a4d5..c6f99bb2 100644[m [1m--- a/src/ui/documentwidget.c[m [1m+++ b/src/ui/documentwidget.c[m [36m@@ -2207,7 +2207,7 @@[m [mstatic void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {[m iString *text = collect_String(joinCStr_StringArray(title, " \u2014 "));[m if (setWindow) {[m /* Longest version for the window title, and omit the icon. */[m [31m- setTitle_MainWindow(get_MainWindow(), text);[m [32m+[m[32m setTitle_Window(as_Window(get_MainWindow()), text);[m setWindow = iFalse;[m }[m const iChar siteIcon = siteIcon_GmDocument(d->view.doc);[m [1mdiff --git a/src/ui/mobile.c b/src/ui/mobile.c[m [1mindex bfcdc8f6..d8b6d7b6 100644[m [1m--- a/src/ui/mobile.c[m [1m+++ b/src/ui/mobile.c[m [36m@@ -1090,6 +1090,10 @@[m [mvoid setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) {[m }[m [m void setupSheetTransition_Mobile(iWidget *sheet, int flags) {[m [32m+[m[32m if (isPromoted_Widget(sheet)) {[m [32m+[m[32m /* This has been promoted to a window, shouldn't animate it. */[m [32m+[m[32m return;[m [32m+[m[32m }[m const iBool isIncoming = (flags & incoming_TransitionFlag) != 0;[m const int dir = flags & dirMask_TransitionFlag;[m if (!isUsingPanelLayout_Mobile()) {[m [1mdiff --git a/src/ui/root.c b/src/ui/root.c[m [1mindex 1dbced78..e93adc57 100644[m [1m--- a/src/ui/root.c[m [1m+++ b/src/ui/root.c[m [36m@@ -318,7 +318,7 @@[m [miPtrArray *onTop_Root(iRoot *d) {[m return d->onTop;[m }[m [m [31m-static iBool handleRootCommands_(iWidget *root, const char *cmd) {[m [32m+[m[32miBool handleRootCommands_Widget(iWidget *root, const char *cmd) {[m iUnused(root);[m if (equal_Command(cmd, "menu.open")) {[m iWidget *button = pointer_Command(cmd);[m [36m@@ -1386,7 +1386,7 @@[m [mvoid createUserInterface_Root(iRoot *d) {[m /* Children of root cover the entire window. */[m setFlags_Widget([m root, resizeChildren_WidgetFlag | fixedSize_WidgetFlag | focusRoot_WidgetFlag, iTrue);[m [31m- setCommandHandler_Widget(root, handleRootCommands_);[m [32m+[m[32m setCommandHandler_Widget(root, handleRootCommands_Widget);[m iWidget *div = makeVDiv_Widget();[m setId_Widget(div, "navdiv");[m addChild_Widget(root, iClob(div));[m [36m@@ -1937,7 +1937,9 @@[m [mstatic void setupMovableElements_Root_(iRoot *d) {[m iWidget *navMenu = findChild_Widget(d->widget, "navbar.menu");[m setFlags_Widget(menuBar, hidden_WidgetFlag, !prefs->menuBar);[m setFlags_Widget(navMenu, hidden_WidgetFlag, prefs->menuBar);[m [31m- iChangeFlags(navBar->flags2, permanentVisualOffset_WidgetFlag2, iFalse);[m [32m+[m[32m if (navBar) {[m [32m+[m[32m iChangeFlags(navBar->flags2, permanentVisualOffset_WidgetFlag2, iFalse);[m [32m+[m[32m }[m if (prefs->bottomNavBar) {[m if (deviceType_App() == phone_AppDeviceType) {[m /* When at the bottom, the navbar is at the top of the bottombar, and gets fully hidden[m [36m@@ -1948,7 +1950,7 @@[m [mstatic void setupMovableElements_Root_(iRoot *d) {[m iRelease(navBar);[m }[m }[m [31m- else {[m [32m+[m[32m else if (navBar) {[m /* On desktop/tablet, a bottom navbar is at the bottom of the main layout. */[m removeChild_Widget(navBar->parent, navBar);[m addChildPos_Widget(div, navBar, back_WidgetAddPos);[m [36m@@ -1958,7 +1960,7 @@[m [mstatic void setupMovableElements_Root_(iRoot *d) {[m deviceType_App() == tablet_AppDeviceType);[m }[m }[m [31m- else {[m [32m+[m[32m else if (navBar) {[m /* In the top navbar layout, the navbar is always the first (or second) child. */[m removeChild_Widget(navBar->parent, navBar);[m if (winBar) {[m [36m@@ -1974,20 +1976,22 @@[m [mstatic void setupMovableElements_Root_(iRoot *d) {[m }[m iRelease(navBar);[m }[m [31m- iChangeFlags(tabBar->flags2, permanentVisualOffset_WidgetFlag2, prefs->bottomTabBar);[m [31m- /* Tab button frames. */[m [31m- iForEach(ObjectList, i, children_Widget(tabBar)) {[m [31m- if (isInstance_Object(i.object, &Class_LabelWidget)) {[m [31m- setNoTopFrame_LabelWidget(i.object, !prefs->bottomTabBar);[m [31m- setNoBottomFrame_LabelWidget(i.object, prefs->bottomTabBar);[m [32m+[m[32m if (tabBar) {[m [32m+[m[32m iChangeFlags(tabBar->flags2, permanentVisualOffset_WidgetFlag2, prefs->bottomTabBar);[m [32m+[m[32m /* Tab button frames. */[m [32m+[m[32m iForEach(ObjectList, i, children_Widget(tabBar)) {[m [32m+[m[32m if (isInstance_Object(i.object, &Class_LabelWidget)) {[m [32m+[m[32m setNoTopFrame_LabelWidget(i.object, !prefs->bottomTabBar);[m [32m+[m[32m setNoBottomFrame_LabelWidget(i.object, prefs->bottomTabBar);[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m /* Adjust safe area paddings. */[m [32m+[m[32m if (deviceType_App() == tablet_AppDeviceType && prefs->bottomTabBar && !prefs->bottomNavBar) {[m [32m+[m[32m tabBar->padding[3] = bottomSafeInset_Mobile();[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m tabBar->padding[3] = 0;[m }[m [31m- }[m [31m- /* Adjust safe area paddings. */[m [31m- if (deviceType_App() == tablet_AppDeviceType && prefs->bottomTabBar && !prefs->bottomNavBar) {[m [31m- tabBar->padding[3] = bottomSafeInset_Mobile();[m [31m- }[m [31m- else {[m [31m- tabBar->padding[3] = 0;[m }[m setTabBarPosition_Widget(docTabs, prefs->bottomTabBar);[m arrange_Widget(d->widget);[m [36m@@ -2205,9 +2209,11 @@[m [miRect visibleRect_Root(const iRoot *d) {[m visRect = intersect_Rect(visRect, init_Rect(usable.x, usable.y, usable.w, usable.h)); [m }[m #endif[m [31m- const int keyboardHeight = get_MainWindow()->keyboardHeight;[m [31m- if (keyboardHeight > bottom) {[m [31m- adjustEdges_Rect(&visRect, 0, 0, -keyboardHeight + bottom, 0);[m [32m+[m[32m if (get_MainWindow()) {[m [32m+[m[32m const int keyboardHeight = get_MainWindow()->keyboardHeight;[m [32m+[m[32m if (keyboardHeight > bottom) {[m [32m+[m[32m adjustEdges_Rect(&visRect, 0, 0, -keyboardHeight + bottom, 0);[m [32m+[m[32m }[m }[m return visRect;[m }[m [1mdiff --git a/src/ui/root.h b/src/ui/root.h[m [1mindex 1ec18bf2..4d3797a0 100644[m [1m--- a/src/ui/root.h[m [1m+++ b/src/ui/root.h[m [36m@@ -56,3 +56,5 @@[m [miRect safeRect_Root (const iRoot *);[m iRect visibleRect_Root (const iRoot *); /* may be obstructed by software keyboard */[m iBool isNarrow_Root (const iRoot *);[m int appIconSize_Root (void);[m [32m+[m [32m+[m[32miBool handleRootCommands_Widget (iWidget *, const char *cmd);[m [1mdiff --git a/src/ui/util.c b/src/ui/util.c[m [1mindex 87a8ec0b..1b637aca 100644[m [1m--- a/src/ui/util.c[m [1m+++ b/src/ui/util.c[m [36m@@ -1383,6 +1383,46 @@[m [mvoid closeMenu_Widget(iWidget *d) {[m setupMenuTransition_Mobile(d, iFalse);[m }[m [m [32m+[m[32miWindow *promoteDialogToWindow_Widget(iWidget *dlg) {[m [32m+[m[32m arrange_Widget(dlg);[m [32m+[m[32m removeChild_Widget(parent_Widget(dlg), dlg);[m [32m+[m[32m setVisualOffset_Widget(dlg, 0, 0, 0);[m [32m+[m[32m setFlags_Widget(dlg, horizontalOffset_WidgetFlag | visualOffset_WidgetFlag |[m [32m+[m[32m centerHorizontal_WidgetFlag | overflowScrollable_WidgetFlag, iFalse);[m [32m+[m[32m /* Check for a dialog heading. */[m [32m+[m[32m printTree_Widget(dlg);[m [32m+[m[32m iWidget *child = child_Widget(dlg, 0);[m [32m+[m[32m iWindow *x = newExtra_Window(dlg);[m [32m+[m[32m if (isInstance_Object(child, &Class_LabelWidget)) {[m [32m+[m[32m iLabelWidget *heading = (iLabelWidget *) child;[m [32m+[m[32m iString *title = copy_String(sourceText_LabelWidget(heading));[m [32m+[m[32m translate_Lang(title);[m [32m+[m[32m setTitle_Window(x, title);[m [32m+[m[32m delete_String(title);[m [32m+[m[32m iRelease(removeChild_Widget(dlg, heading));[m [32m+[m[32m arrange_Widget(dlg);[m [32m+[m[32m }[m [32m+[m[32m addExtraWindow_App(x);[m [32m+[m[32m SDL_ShowWindow(x->win);[m [32m+[m[32m SDL_RaiseWindow(x->win);[m [32m+[m[32m setActiveWindow_App(x);[m [32m+[m[32m return x;[m [32m+[m[32m}[m [32m+[m [32m+[m[32miBool isPromoted_Widget(iWidget *dlg) {[m [32m+[m[32m return type_Window(window_Widget(dlg)) == extra_WindowType;[m [32m+[m[32m}[m [32m+[m [32m+[m[32mvoid destroyDialog_Widget(iWidget *dlg) {[m [32m+[m[32m if (isPromoted_Widget(dlg)) {[m [32m+[m[32m /* Promoted dialog. */[m [32m+[m[32m closeWindow_App(window_Widget(dlg));[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m destroy_Widget(dlg);[m [32m+[m[32m }[m [32m+[m[32m}[m [32m+[m iLabelWidget *findMenuItem_Widget(iWidget *menu, const char *command) {[m iForEach(ObjectList, i, children_Widget(menu)) {[m if (isInstance_Object(i.object, &Class_LabelWidget)) {[m [36m@@ -1653,9 +1693,11 @@[m [mstatic iBool tabSwitcher_(iWidget *tabs, const char *cmd) {[m iWidget *nextTabs = findChild_Widget(otherRoot_Window(get_Window(), tabs->root)->widget,[m "doctabs");[m iWidget *nextPages = findChild_Widget(nextTabs, "tabs.pages");[m [31m- tabIndex = (int) (dir < 0 ? childCount_Widget(nextPages) - 1 : 0);[m [31m- showTabPage_Widget(nextTabs, child_Widget(nextPages, tabIndex));[m [31m- postCommand_App("keyroot.next");[m [32m+[m[32m if (nextPages) {[m [32m+[m[32m tabIndex = (int) (dir < 0 ? childCount_Widget(nextPages) - 1 : 0);[m [32m+[m[32m showTabPage_Widget(nextTabs, child_Widget(nextPages, tabIndex));[m [32m+[m[32m postCommand_App("keyroot.next");[m [32m+[m[32m }[m }[m else {[m showTabPage_Widget(tabs, child_Widget(pages, tabIndex + dir));[m [36m@@ -1689,10 +1731,12 @@[m [miWidget *makeTabs_Widget(iWidget *parent) {[m }[m [m void setTabBarPosition_Widget(iWidget *tabs, iBool atBottom) {[m [31m- iWidget *buttons = findChild_Widget(tabs, "tabs.buttons");[m [31m- removeChild_Widget(tabs, buttons);[m [31m- addChildPos_Widget(tabs, buttons, atBottom ? back_WidgetAddPos : front_WidgetAddPos);[m [31m- iRelease(buttons);[m [32m+[m[32m if (tabs) {[m [32m+[m[32m iWidget *buttons = findChild_Widget(tabs, "tabs.buttons");[m [32m+[m[32m removeChild_Widget(tabs, buttons);[m [32m+[m[32m addChildPos_Widget(tabs, buttons, atBottom ? back_WidgetAddPos : front_WidgetAddPos);[m [32m+[m[32m iRelease(buttons);[m [32m+[m[32m }[m }[m [m void setVerticalTabBar_Widget(iWidget *tabs) {[m [36m@@ -1891,10 +1935,12 @@[m [msize_t tabPageIndex_Widget(const iWidget *tabs, const iAnyObject *page) {[m }[m [m const iWidget *currentTabPage_Widget(const iWidget *tabs) {[m [31m- iWidget *pages = findChild_Widget(tabs, "tabs.pages");[m [31m- iConstForEach(ObjectList, i, pages->children) {[m [31m- if (isVisible_Widget(i.object)) {[m [31m- return constAs_Widget(i.object);[m [32m+[m[32m if (tabs) {[m [32m+[m[32m iWidget *pages = findChild_Widget(tabs, "tabs.pages");[m [32m+[m[32m iConstForEach(ObjectList, i, pages->children) {[m [32m+[m[32m if (isVisible_Widget(i.object)) {[m [32m+[m[32m return constAs_Widget(i.object);[m [32m+[m[32m }[m }[m }[m return NULL;[m [1mdiff --git a/src/ui/util.h b/src/ui/util.h[m [1mindex bb661944..12c91ee0 100644[m [1m--- a/src/ui/util.h[m [1m+++ b/src/ui/util.h[m [36m@@ -35,7 +35,8 @@[m [miDeclareType(Click)[m iDeclareType(Widget)[m iDeclareType(LabelWidget)[m iDeclareType(InputWidget)[m [31m-[m [32m+[m[32miDeclareType(Window)[m [32m+[m[41m [m iBool isCommand_SDLEvent (const SDL_Event *d);[m iBool isCommand_UserEvent (const SDL_Event *, const char *cmd);[m const char * command_UserEvent (const SDL_Event *);[m [36m@@ -395,6 +396,10 @@[m [miWidget * makeUserDataImporter_Dialog (const iString *archivePath);[m const char * languageId_String (const iString *menuItemLabel);[m int languageIndex_CStr (const char *langId);[m [m [32m+[m[32miWindow * promoteDialogToWindow_Widget (iWidget *);[m [32m+[m[32miBool isPromoted_Widget (iWidget *);[m [32m+[m[32mvoid destroyDialog_Widget (iWidget *);[m [32m+[m /*-----------------------------------------------------------------------------------------------*/[m [m iDeclareType(PerfTimer)[m [1mdiff --git a/src/ui/widget.c b/src/ui/widget.c[m [1mindex 730832d8..7e3b6099 100644[m [1m--- a/src/ui/widget.c[m [1m+++ b/src/ui/widget.c[m [36m@@ -231,8 +231,13 @@[m [mstatic void aboutToBeDestroyed_Widget_(iWidget *d) {[m }[m }[m [m [32m+[m[32miLocalDef iBool isRoot_Widget_(const iWidget *d) {[m [32m+[m[32m return d && d->root && d->root->widget == d;[m [32m+[m[32m}[m [32m+[m void destroy_Widget(iWidget *d) {[m if (d) {[m [32m+[m[32m iAssert(!isRoot_Widget_(d));[m if (isVisible_Widget(d)) {[m postRefresh_App();[m }[m [36m@@ -263,7 +268,7 @@[m [mvoid setFlags_Widget(iWidget *d, int64_t flags, iBool set) {[m flags &= ~drawKey_WidgetFlag;[m }[m iChangeFlags(d->flags, flags, set);[m [31m- if (flags & keepOnTop_WidgetFlag) {[m [32m+[m[32m if (flags & keepOnTop_WidgetFlag && !isRoot_Widget_(d)) {[m iPtrArray *onTop = onTop_Root(d->root);[m if (set) {[m iAssert(indexOf_PtrArray(onTop, d) == iInvalidPos);[m [36m@@ -274,11 +279,13 @@[m [mvoid setFlags_Widget(iWidget *d, int64_t flags, iBool set) {[m iAssert(indexOf_PtrArray(onTop, d) == iInvalidPos);[m }[m }[m [32m+[m[32m#if !defined (NDEBUG)[m if (d->flags & arrangeWidth_WidgetFlag &&[m d->flags & resizeToParentWidth_WidgetFlag) {[m printf("[Widget] Conflicting flags for ");[m identify_Widget(d);[m }[m [32m+[m[32m#endif[m }[m }[m [m [36m@@ -334,6 +341,7 @@[m [miWindow *window_Widget(const iAnyObject *d) {[m }[m [m void showCollapsed_Widget(iWidget *d, iBool show) {[m [32m+[m[32m if (!d) return;[m const iBool isVisible = !(d->flags & hidden_WidgetFlag);[m if ((isVisible && !show) || (!isVisible && show)) {[m setFlags_Widget(d, hidden_WidgetFlag, !show);[m [36m@@ -377,8 +385,10 @@[m [mvoid setRoot_Widget(iWidget *d, iRoot *root) {[m iAssert(indexOf_PtrArray(onTop_Root(root), d) == iInvalidPos);[m /* Move it over the new root's onTop list. */[m removeOne_PtrArray(onTop_Root(d->root), d);[m [31m- iAssert(indexOf_PtrArray(onTop_Root(d->root), d) == iInvalidPos);[m [31m- pushBack_PtrArray(onTop_Root(root), d);[m [32m+[m[32m if (d != root->widget) {[m [32m+[m[32m iAssert(indexOf_PtrArray(onTop_Root(d->root), d) == iInvalidPos);[m [32m+[m[32m pushBack_PtrArray(onTop_Root(root), d);[m [32m+[m[32m }[m }[m d->root = root;[m iForEach(ObjectList, i, d->children) {[m [36m@@ -997,6 +1007,15 @@[m [mvoid arrange_Widget(iWidget *d) {[m clampCenteredInRoot_Widget_(d);[m notifySizeChanged_Widget_(d);[m d->root->didChangeArrangement = iTrue;[m [32m+[m[32m if (type_Window(window_Widget(d)) == extra_WindowType &&[m [32m+[m[32m (d == root_Widget(d) || d->parent == root_Widget(d))) {[m [32m+[m[32m /* Size of extra windows will change depending on the contents. */[m [32m+[m[32m iWindow *win = window_Widget(d);[m [32m+[m[32m SDL_SetWindowSize(win->win,[m [32m+[m[32m width_Widget(d) / win->pixelRatio,[m [32m+[m[32m height_Widget(d) / win->pixelRatio);[m [32m+[m[32m win->size = d->rect.size;[m [32m+[m[32m }[m }[m }[m [m [36m@@ -1157,19 +1176,26 @@[m [mvoid unhover_Widget(void) {[m *hover = NULL;[m }[m [m [32m+[m[32miLocalDef iBool redispatchEvent_Widget_(iWidget *d, iWidget *dst, const SDL_Event *ev) {[m [32m+[m[32m if (d != dst) {[m [32m+[m[32m return dispatchEvent_Widget(dst, ev);[m [32m+[m[32m }[m [32m+[m[32m return iFalse;[m [32m+[m[32m}[m [32m+[m iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {[m if (!d->parent) {[m if (window_Widget(d)->focus && window_Widget(d)->focus->root == d->root &&[m (isKeyboardEvent_(ev) || ev->type == SDL_USEREVENT)) {[m /* Root dispatches keyboard events directly to the focused widget. */[m [31m- if (dispatchEvent_Widget(window_Widget(d)->focus, ev)) {[m [32m+[m[32m if (redispatchEvent_Widget_(d, window_Widget(d)->focus, ev)) {[m return iTrue;[m }[m }[m /* Root offers events first to widgets on top. */[m iReverseForEach(PtrArray, i, d->root->onTop) {[m iWidget *widget = *i.value;[m [31m- if (isVisible_Widget(widget) && dispatchEvent_Widget(widget, ev)) {[m [32m+[m[32m if (isVisible_Widget(widget) && redispatchEvent_Widget_(d, widget, ev)) {[m #if 0[m if (ev->type == SDL_KEYDOWN) {[m printf("[%p] %s:'%s' (on top) ate the key\n",[m [36m@@ -1218,6 +1244,7 @@[m [miBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {[m handle the events first. */[m iReverseForEach(ObjectList, i, d->children) {[m iWidget *child = as_Widget(i.object);[m [32m+[m[32m iAssert(child != d); /* cannot be child of self */[m iAssert(child->root == d->root);[m if (child == window_Widget(d)->focus &&[m (isKeyboardEvent_(ev) || ev->type == SDL_USEREVENT)) {[m [36m@@ -1697,10 +1724,6 @@[m [mvoid drawBackground_Widget(const iWidget *d) {[m [m int drawCount_;[m [m [31m-static iBool isRoot_Widget_(const iWidget *d) {[m [31m- return d == d->root->widget;[m [31m-}[m [31m-[m iLocalDef iBool isFullyContainedByOther_Rect(const iRect d, const iRect other) {[m if (isEmpty_Rect(other)) {[m /* Nothing is contained by empty. */[m [36m@@ -2350,7 +2373,7 @@[m [mvoid refresh_Widget(const iAnyObject *d) {[m [m void raise_Widget(iWidget *d) {[m iPtrArray *onTop = onTop_Root(d->root);[m [31m- if (d->flags & keepOnTop_WidgetFlag) {[m [32m+[m[32m if (d->flags & keepOnTop_WidgetFlag && !isRoot_Widget_(d)) {[m iAssert(indexOf_PtrArray(onTop, d) != iInvalidPos);[m removeOne_PtrArray(onTop, d);[m pushBack_PtrArray(onTop, d);[m [1mdiff --git a/src/ui/window.c b/src/ui/window.c[m [1mindex 108c90b7..a1c33923 100644[m [1m--- a/src/ui/window.c[m [1m+++ b/src/ui/window.c[m [36m@@ -405,8 +405,7 @@[m [mstatic float displayScale_Window_(const iWindow *d) {[m }[m [m static void drawBlank_Window_(iWindow *d) {[m [31m-// const iColor bg = get_Color(uiBackground_ColorId);[m [31m- const iColor bg = { 128, 128, 128, 255 }; /* TODO: Have no root yet. */[m [32m+[m[32m const iColor bg = default_Color(uiBackground_ColorId);[m SDL_SetRenderDrawColor(d->render, bg.r, bg.g, bg.b, 255);[m SDL_RenderClear(d->render);[m SDL_RenderPresent(d->render);[m [36m@@ -616,6 +615,9 @@[m [mvoid deinit_Window(iWindow *d) {[m if (d->type == popup_WindowType) {[m removePopup_App(d);[m }[m [32m+[m[32m else if (d->type == extra_WindowType) {[m [32m+[m[32m removeExtraWindow_App(d);[m [32m+[m[32m }[m deinitRoots_Window_(d);[m delete_Text(d->text);[m SDL_DestroyRenderer(d->render);[m [36m@@ -785,25 +787,28 @@[m [miRoot *otherRoot_Window(const iWindow *d, iRoot *root) {[m return root == d->roots[0] && d->roots[1] ? d->roots[1] : d->roots[0];[m }[m [m [31m-static void invalidate_MainWindow_(iMainWindow *d, iBool forced) {[m [31m- if (d && (!d->base.isInvalidated || forced)) {[m [31m- d->base.isInvalidated = iTrue;[m [31m- if (d->enableBackBuf && d->backBuf) {[m [31m- SDL_DestroyTexture(d->backBuf);[m [31m- d->backBuf = NULL;[m [32m+[m[32mstatic void invalidate_Window_(iAnyWindow *d, iBool forced) {[m [32m+[m[32m iWindow *w = as_Window(d);[m [32m+[m[32m if (w && (!w->isInvalidated || forced)) {[m [32m+[m[32m w->isInvalidated = iTrue;[m [32m+[m[32m if (w->type == main_WindowType) {[m [32m+[m[32m iMainWindow *mw = as_MainWindow(w);[m [32m+[m[32m if (mw->enableBackBuf && mw->backBuf) {[m [32m+[m[32m SDL_DestroyTexture(mw->backBuf);[m [32m+[m[32m mw->backBuf = NULL;[m [32m+[m[32m }[m }[m [31m- resetFontCache_Text(text_Window(d));[m [32m+[m[32m resetFontCache_Text(text_Window(w));[m postCommand_App("theme.changed auto:1"); /* forces UI invalidation */[m }[m }[m [m void invalidate_Window(iAnyWindow *d) {[m [31m- if (type_Window(d) == main_WindowType) {[m [31m- invalidate_MainWindow_(as_MainWindow(d), iFalse);[m [31m- }[m [31m- else {[m [31m- iAssert(type_Window(d) == main_WindowType);[m [31m- }[m [32m+[m[32m invalidate_Window_(d, iFalse);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic void invalidate_MainWindow_(iMainWindow *d, iBool forced) {[m [32m+[m[32m invalidate_Window_(as_Window(d), forced);[m }[m [m static iBool isNormalPlacement_MainWindow_(const iMainWindow *d) {[m [36m@@ -890,26 +895,72 @@[m [mstatic iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {[m checkPixelRatioChange_Window_(as_Window(d));[m return iTrue;[m #endif[m [32m+[m[32m case SDL_WINDOWEVENT_CLOSE:[m [32m+[m[32m if (d->type == extra_WindowType) {[m [32m+[m[32m closeWindow_App(d);[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m [32m+[m[32m return iFalse;[m case SDL_WINDOWEVENT_EXPOSED:[m d->isExposed = iTrue;[m [31m-// checkPixelRatioChange_Window_(as_Window(d));[m [32m+[m[32m if (d->type == extra_WindowType) {[m [32m+[m[32m checkPixelRatioChange_Window_(d);[m [32m+[m[32m }[m postRefresh_App();[m return iTrue;[m case SDL_WINDOWEVENT_RESTORED:[m case SDL_WINDOWEVENT_SHOWN:[m postRefresh_App();[m return iTrue;[m [32m+[m[32m case SDL_WINDOWEVENT_MOVED:[m [32m+[m[32m if (d->type == extra_WindowType) {[m [32m+[m[32m checkPixelRatioChange_Window_(d);[m [32m+[m[32m }[m [32m+[m[32m return iFalse;[m [32m+[m[32m case SDL_WINDOWEVENT_FOCUS_GAINED:[m [32m+[m[32m if (d->type == extra_WindowType) {[m [32m+[m[32m d->focusGainedAt = SDL_GetTicks();[m [32m+[m[32m setCapsLockDown_Keys(iFalse);[m [32m+[m[32m postCommand_App("window.focus.gained");[m [32m+[m[32m d->isExposed = iTrue;[m [32m+[m[32m setActiveWindow_App(d);[m [32m+[m[32m#if !defined (iPlatformDesktop)[m [32m+[m[32m /* Returned to foreground, may have lost buffered content. */[m [32m+[m[32m invalidate_Window(d);[m [32m+[m[32m postCommand_App("window.unfreeze");[m [32m+[m[32m#endif[m [32m+[m[32m }[m [32m+[m[32m return iFalse;[m [32m+[m[32m case SDL_WINDOWEVENT_TAKE_FOCUS:[m [32m+[m[32m if (d->type == extra_WindowType) {[m [32m+[m[32m SDL_SetWindowInputFocus(d->win);[m [32m+[m[32m postRefresh_App();[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m [32m+[m[32m return iFalse;[m case SDL_WINDOWEVENT_FOCUS_LOST:[m [31m- /* Popup windows are currently only used for menus. */[m [31m- closeMenu_Widget(d->roots[0]->widget);[m [32m+[m[32m if (d->type == popup_WindowType) {[m [32m+[m[32m /* Popup windows are currently only used for menus. */[m [32m+[m[32m closeMenu_Widget(d->roots[0]->widget);[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m postCommand_App("window.focus.lost");[m [32m+[m[32m closePopups_App(iTrue);[m [32m+[m[32m }[m return iTrue;[m case SDL_WINDOWEVENT_LEAVE:[m unhover_Widget();[m d->isMouseInside = iFalse;[m [32m+[m[32m if (d->type == extra_WindowType) {[m [32m+[m[32m postCommand_App("window.mouse.exited");[m [32m+[m[32m }[m postRefresh_App();[m return iTrue;[m case SDL_WINDOWEVENT_ENTER:[m d->isMouseInside = iTrue;[m [32m+[m[32m if (d->type == extra_WindowType) {[m [32m+[m[32m postCommand_App("window.mouse.entered");[m [32m+[m[32m }[m return iTrue;[m }[m return iFalse;[m [36m@@ -1078,7 +1129,7 @@[m [mstatic iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent[m return iTrue;[m case SDL_WINDOWEVENT_CLOSE:[m #if defined (iPlatformAppleDesktop)[m [31m- closeWindow_App(d);[m [32m+[m[32m closeWindow_App(as_Window(d));[m #else[m if (numWindows_App() == 1) {[m postCommand_App("quit");[m [36m@@ -1106,7 +1157,8 @@[m [mvoid updateHover_Window(iWindow *d) {[m }[m [m iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {[m [31m- iMainWindow *mw = (type_Window(d) == main_WindowType ? as_MainWindow(d) : NULL);[m [32m+[m[32m iMainWindow *mw = (type_Window(d) == main_WindowType ? as_MainWindow(d) : NULL);[m [32m+[m[32m iWindow * extraw = (type_Window(d) == extra_WindowType ? d : NULL);[m switch (ev->type) {[m #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)[m case SDL_SYSWMEVENT: {[m [36m@@ -1128,8 +1180,8 @@[m [miBool processEvent_Window(iWindow *d, const SDL_Event *ev) {[m }[m case SDL_RENDER_TARGETS_RESET:[m case SDL_RENDER_DEVICE_RESET: { [m [31m- if (mw) {[m [31m- invalidate_MainWindow_(mw, iTrue /* force full reset */);[m [32m+[m[32m if (mw || extraw) {[m [32m+[m[32m invalidate_Window_(d, iTrue /* force full reset */);[m }[m break;[m }[m [36m@@ -1252,7 +1304,7 @@[m [miBool processEvent_Window(iWindow *d, const SDL_Event *ev) {[m updateMetrics_Root(d->roots[i]);[m }[m }[m [31m- if (isCommand_UserEvent(&event, "lang.changed") && mw) {[m [32m+[m[32m if (isCommand_UserEvent(&event, "lang.changed") && (mw || extraw)) {[m #if defined (LAGRANGE_MAC_MENUBAR)[m /* Retranslate the menus. */[m removeMacMenus_();[m [36m@@ -1423,8 +1475,10 @@[m [mvoid draw_Window(iWindow *d) {[m drawCount_ = 0;[m #endif[m }[m [31m- drawRectThickness_Paint(&p, (iRect){ zero_I2(), sub_I2(d->size, one_I2()) }, gap_UI / 4,[m [31m- root->widget->frameColor);[m [32m+[m[32m if (type_Window(d) == popup_WindowType) {[m [32m+[m[32m drawRectThickness_Paint(&p, (iRect){ zero_I2(), sub_I2(d->size, one_I2()) }, gap_UI / 4,[m [32m+[m[32m root->widget->frameColor);[m [32m+[m[32m }[m setCurrent_Root(NULL);[m SDL_RenderPresent(d->render);[m isDrawing_ = iFalse;[m [36m@@ -1518,6 +1572,10 @@[m [mvoid draw_MainWindow(iMainWindow *d) {[m iRoot *root = d->base.roots[i];[m if (root) {[m /* Some widgets may need a just-in-time visual update. */[m [32m+[m[32m if (root->didChangeArrangement && root->window->type == extra_WindowType) {[m [32m+[m[32m iWindow *x = root->window;[m [32m+[m[32m SDL_SetWindowSize(x->win, x->size.x / x->pixelRatio, x->size.y / x->pixelRatio);[m [32m+[m[32m }[m notifyVisualOffsetChange_Root(root);[m root->didChangeArrangement = iFalse;[m }[m [36m@@ -1605,8 +1663,8 @@[m [mvoid resize_MainWindow(iMainWindow *d, int w, int h) {[m }[m }[m [m [31m-void setTitle_MainWindow(iMainWindow *d, const iString *title) {[m [31m- SDL_SetWindowTitle(d->base.win, cstr_String(title));[m [32m+[m[32mvoid setTitle_Window(iWindow *d, const iString *title) {[m [32m+[m[32m SDL_SetWindowTitle(d->win, cstr_String(title));[m iLabelWidget *bar = findChild_Widget(get_Root()->widget, "winbar.title");[m if (bar) {[m updateText_LabelWidget(bar, title);[m [36m@@ -2034,22 +2092,46 @@[m [miWindow *newPopup_Window(iInt2 screenPos, iWidget *rootWidget) {[m #if defined (iPlatformAppleDesktop)[m hideTitleBar_MacOS(win); /* make it a borderless window, but retain shadow */[m #endif[m [31m- /* At least on macOS, with an external display on the left (negative coordinates), the [m [32m+[m[32m /* At least on macOS, with an external display on the left (negative coordinates), the[m window will not be correct placed during creation. Ensure it ends up on the right display. */[m SDL_SetWindowPosition(win->win, winRect.pos.x, winRect.pos.y);[m SDL_SetWindowSize(win->win, winRect.size.x, winRect.size.y);[m [31m- win->pixelRatio = pixelRatio;[m [31m- iRoot *root = new_Root();[m [31m- win->roots[0] = root;[m [31m- win->keyRoot = root;[m [31m- root->widget = rootWidget;[m [31m- root->window = win;[m [32m+[m[32m win->pixelRatio = pixelRatio;[m [32m+[m[32m iRoot *root = new_Root();[m [32m+[m[32m win->roots[0] = root;[m [32m+[m[32m win->keyRoot = root;[m [32m+[m[32m root->widget = rootWidget;[m [32m+[m[32m root->window = win;[m rootWidget->rect.pos = zero_I2();[m setRoot_Widget(rootWidget, root);[m setDrawBufferEnabled_Widget(rootWidget, iFalse);[m setForceSoftwareRender_App(oldSw);[m [31m-#if !defined (NDEBUG)[m [32m+[m[32m#if !defined(NDEBUG)[m stop_PerfTimer(newPopup_Window);[m #endif[m return win;[m }[m [32m+[m [32m+[m[32miWindow *newExtra_Window(iWidget *rootWidget) {[m [32m+[m[32m const float pixelRatio = get_Window()->pixelRatio;[m [32m+[m[32m iRect winRect = (iRect){ init1_I2(-1), divf_I2(rootWidget->rect.size, pixelRatio) };[m [32m+[m[32m iWindow *win = new_Window(extra_WindowType, winRect, 0);[m [32m+[m[32m win->pixelRatio = pixelRatio;[m [32m+[m[32m iRoot *root = new_Root();[m [32m+[m[32m win->roots[0] = root;[m [32m+[m[32m win->keyRoot = root;[m [32m+[m[32m /* Make a simple root widget that sizes itself according to the actual root. */[m [32m+[m[32m iWidget *frameRoot = new_Widget();[m [32m+[m[32m setFlags_Widget(frameRoot, arrangeSize_WidgetFlag | focusRoot_WidgetFlag, iTrue);[m [32m+[m[32m setCommandHandler_Widget(frameRoot, handleRootCommands_Widget);[m [32m+[m[32m addChild_Widget(frameRoot, rootWidget);[m [32m+[m[32m iRelease(rootWidget);[m [32m+[m[32m arrange_Widget(frameRoot);[m [32m+[m[32m root->widget = frameRoot;[m [32m+[m[32m root->window = win;[m [32m+[m[32m rootWidget->rect.pos = zero_I2();[m [32m+[m[32m setRoot_Widget(frameRoot, root);[m [32m+[m[32m setDrawBufferEnabled_Widget(frameRoot, iFalse);[m [32m+[m[32m setDrawBufferEnabled_Widget(rootWidget, iFalse);[m [32m+[m[32m return win;[m[41m [m [32m+[m[32m}[m [1mdiff --git a/src/ui/window.h b/src/ui/window.h[m [1mindex d257cfd6..2c93e45b 100644[m [1m--- a/src/ui/window.h[m [1m+++ b/src/ui/window.h[m [36m@@ -34,6 +34,7 @@[m [mextern const iMenuItem topLevelMenus_Window[6];[m [m enum iWindowType {[m main_WindowType,[m [32m+[m[32m extra_WindowType,[m popup_WindowType,[m };[m [m [36m@@ -147,6 +148,7 @@[m [mint numRoots_Window (const iWindow *);[m //iRoot * findRoot_Window (const iWindow *, const iWidget *widget);[m iRoot * otherRoot_Window (const iWindow *, iRoot *root);[m [m [32m+[m[32mvoid setTitle_Window (iWindow *, const iString *title);[m iBool processEvent_Window (iWindow *, const SDL_Event *);[m iBool dispatchEvent_Window (iWindow *, const SDL_Event *);[m void invalidate_Window (iAnyWindow *); /* discard all cached graphics */[m [36m@@ -167,13 +169,22 @@[m [miLocalDef iBool isExposed_Window(const iWindow *d) {[m return d->isExposed;[m }[m [m [32m+[m[32miLocalDef iBool isDrawFrozen_Window(const iWindow *d) {[m [32m+[m[32m if (d && d->type == main_WindowType) {[m [32m+[m[32m return ((const iMainWindow *) d)->isDrawFrozen;[m [32m+[m[32m }[m [32m+[m[32m return iFalse;[m [32m+[m[32m}[m [32m+[m iLocalDef iWindow *as_Window(iAnyWindow *d) {[m [31m- iAssert(type_Window(d) == main_WindowType || type_Window(d) == popup_WindowType);[m [32m+[m[32m iAssert(type_Window(d) == main_WindowType || type_Window(d) == extra_WindowType ||[m [32m+[m[32m type_Window(d) == popup_WindowType);[m return (iWindow *) d;[m }[m [m iLocalDef const iWindow *constAs_Window(const iAnyWindow *d) {[m [31m- iAssert(type_Window(d) == main_WindowType || type_Window(d) == popup_WindowType);[m [32m+[m[32m iAssert(type_Window(d) == main_WindowType || type_Window(d) == extra_WindowType ||[m [32m+[m[32m type_Window(d) == popup_WindowType);[m return (const iWindow *) d;[m }[m [m [36m@@ -188,7 +199,6 @@[m [miLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) {[m return &d->base;[m }[m [m [31m-void setTitle_MainWindow (iMainWindow *, const iString *title);[m void setSnap_MainWindow (iMainWindow *, int snapMode);[m void setFreezeDraw_MainWindow (iMainWindow *, iBool freezeDraw);[m void setKeyboardHeight_MainWindow (iMainWindow *, int height);[m [36m@@ -224,4 +234,5 @@[m [miLocalDef const iMainWindow *constAs_MainWindow(const iAnyWindow *d) {[m [m /*----------------------------------------------------------------------------------------------*/[m [m [31m-iWindow * newPopup_Window (iInt2 screenPos, iWidget *rootWidget);[m [32m+[m[32miWindow * newPopup_Window (iInt2 screenPos, iWidget *rootWidget);[m [32m+[m[32miWindow * newExtra_Window (iWidget *rootWidget);[m
text/gemini; charset=utf-8
This content has been proxied by September (3851b).