diff --git a/src/app.c b/src/app.c

index 91b3a06d..c1c1da27 100644

--- a/src/app.c

+++ b/src/app.c

@@ -118,6 +118,7 @@ struct Impl_App {

 iVisited *   visited;

 iBookmarks * bookmarks;

 iMainWindow *window;

+ iPtrArray popupWindows;

 iSortedArray tickers; /* per-frame callbacks, used for animations */

 uint32_t     lastTickerTime;

 uint32_t     elapsedSinceLastTicker;

@@ -801,6 +802,7 @@ static void init_App_(iApp *d, int argc, char **argv) {

         d->initialWindowRect.size.y = toInt_String(value_CommandLineArg(arg, 0));

     }

 }

+ init_PtrArray(&d->popupWindows);

 d->window = new_MainWindow(d->initialWindowRect);

 load_Visited(d->visited, dataDir_App_());

 load_Bookmarks(d->bookmarks, dataDir_App_());

@@ -853,6 +855,11 @@ static void init_App_(iApp *d, int argc, char **argv) {

}



static void deinit_App(iApp *d) {

+ iReverseForEach(PtrArray, i, &d->popupWindows) {

+ delete_Window(i.ptr);

+ }

+ iAssert(isEmpty_PtrArray(&d->popupWindows));

+ deinit_PtrArray(&d->popupWindows);

#if defined (LAGRANGE_ENABLE_IDLE_SLEEP)

 SDL_RemoveTimer(d->sleepTimer);

#endif

@@ -1086,6 +1093,15 @@ static iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *ev

 return SDL_PollEvent(event);

}



+static const iPtrArray *listWindows_App_(const iApp *d) {

+ iPtrArray *list = collectNew_PtrArray();

+ iReverseConstForEach(PtrArray, i, &d->popupWindows) {

+ pushBack_PtrArray(list, i.ptr);

+ }

+ pushBack_PtrArray(list, d->window);

+ return list;

+}

+

void processEvents_App(enum iAppEventMode eventMode) {

 iApp *d = &app_;

 iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */

@@ -1125,17 +1141,17 @@ void processEvents_App(enum iAppEventMode eventMode) {

#if defined (iPlatformAppleMobile)

             updateNowPlayingInfo_iOS();

#endif

- setFreezeDraw_Window(as_Window(d), iTrue);

+ setFreezeDraw_MainWindow(d->window, iTrue);

             savePrefs_App_(d);

             saveState_App_(d);

             break;

         case SDL_APP_TERMINATING:

- setFreezeDraw_Window(as_Window(d), iTrue);

+ setFreezeDraw_MainWindow(d->window, iTrue);

             savePrefs_App_(d);

             saveState_App_(d);

             break;

         case SDL_DROPFILE: {

- iBool wasUsed = processEvent_MainWindow(d->window, &ev);

+ iBool wasUsed = processEvent_Window(as_Window(d->window), &ev);

             if (!wasUsed) {

                 iBool newTab = iFalse;

                 if (elapsedSeconds_Time(&d->lastDropTime) < 0.1) {

@@ -1175,23 +1191,6 @@ void processEvents_App(enum iAppEventMode eventMode) {

             }

             d->isIdling = iFalse;

#endif

- if (ev.type == SDL_USEREVENT && ev.user.code == arrange_UserEventCode) {

- printf("[App] rearrange\n");

- resize_MainWindow(d->window, -1, -1);

- iForIndices(i, d->window->base.roots) {

- if (d->window->base.roots[i]) {

- d->window->base.roots[i]->pendingArrange = iFalse;

- }

- }

-// if (ev.user.data2 == d->window->roots[0]) {

-// arrange_Widget(d->window->roots[0]->widget);

-// }

-// else if (d->window->roots[1]) {

-// arrange_Widget(d->window->roots[1]->widget);

-// }

-// postRefresh_App();

- continue;

- }

             gotEvents = iTrue;

             /* Keyboard modifier mapping. */

             if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) {

@@ -1268,10 +1267,22 @@ void processEvents_App(enum iAppEventMode eventMode) {

                 }

             }

#endif

- d->window->base.lastHover = d->window->base.hover;

- iBool wasUsed = processEvent_MainWindow(d->window, &ev);

+ /* Per-window processing. */

+ iBool wasUsed = iFalse;

+ const iPtrArray *windows = listWindows_App_(d);

+ iConstForEach(PtrArray, iter, windows) {

+ iWindow *window = iter.ptr;

+ setCurrent_Window(window);

+ window->lastHover = window->hover;

+ wasUsed = processEvent_Window(window, &ev);

+ if (ev.type == SDL_MOUSEMOTION) {

+ break;

+ }

+ if (wasUsed) break;

+ }

+ setCurrent_Window(d->window);

             if (!wasUsed) {

- /* There may be a key bindings for this. */

+ /* There may be a key binding for this. */

                 wasUsed = processEvent_Keys(&ev);

             }

             if (!wasUsed) {

@@ -1289,24 +1300,32 @@ void processEvents_App(enum iAppEventMode eventMode) {

                 handleCommand_MacOS(command_UserEvent(&ev));

#endif

                 if (isMetricsChange_UserEvent(&ev)) {

- iForIndices(i, d->window->base.roots) {

- iRoot *root = d->window->base.roots[i];

- if (root) {

- arrange_Widget(root->widget);

- }

+ iConstForEach(PtrArray, iter, windows) {

+ iWindow *window = iter.ptr;

+ iForIndices(i, window->roots) {

+ iRoot *root = window->roots[i];

+ if (root) {

+ arrange_Widget(root->widget);

+ }

+ } 

                     }

                 }

                 if (!wasUsed) {

                     /* No widget handled the command, so we'll do it. */

+ setCurrent_Window(d->window);

                     handleCommand_App(ev.user.data1);

                 }

                 /* Allocated by postCommand_Apps(). */

                 free(ev.user.data1);

             }

- /* Update when hover has changed. */

- if (d->window->base.lastHover != d->window->base.hover) {

- refresh_Widget(d->window->base.lastHover);

- refresh_Widget(d->window->base.hover);

+ /* Refresh after hover changes. */ {

+ iConstForEach(PtrArray, iter, windows) {

+ iWindow *window = iter.ptr;

+ if (window->lastHover != window->hover) {

+ refresh_Widget(window->lastHover);

+ refresh_Widget(window->hover);

+ }

+ }

             }

             break;

         }

@@ -1394,25 +1413,46 @@ static int run_App_(iApp *d) {



void refresh_App(void) {

 iApp *d = &app_;

- iForIndices(i, d->window->base.roots) {

- iRoot *root = d->window->base.roots[i];

- if (root) {

- destroyPending_Root(root);

- }

- }

#if defined (LAGRANGE_ENABLE_IDLE_SLEEP)

 if (d->warmupFrames == 0 && d->isIdling) {

     return;

 }

#endif

+ const iPtrArray *windows = listWindows_App_(d);

+ /* Destroy pending widgets. */ {

+ iConstForEach(PtrArray, j, windows) { 

+ iWindow *win = j.ptr;

+ setCurrent_Window(win);

+ iForIndices(i, win->roots) {

+ iRoot *root = win->roots[i];

+ if (root) {

+ destroyPending_Root(root);

+ }

+ }

+ }

+ }

+ /* TODO: Pending refresh is window-specific. */

 if (!exchange_Atomic(&d->pendingRefresh, iFalse)) {

     return;

 }

-// iTime draw;

-// initCurrent_Time(&draw);

- draw_MainWindow(d->window);

-// printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000));

-// fflush(stdout);

+ /* Draw each window. */ {

+ iConstForEach(PtrArray, j, windows) {

+ iWindow *win = j.ptr;

+ setCurrent_Window(win);

+ switch (win->type) {

+ case main_WindowType:

+ // iTime draw;

+ // initCurrent_Time(&draw);

+ draw_MainWindow(as_MainWindow(win));

+ // printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000));

+ // fflush(stdout);

+ break;

+ default:

+ draw_Window(win);

+ break; 

+ }

+ }

+ }

 if (d->warmupFrames > 0) {

     d->warmupFrames--;

 }

@@ -1485,12 +1525,6 @@ void postRefresh_App(void) {

 }

}



-void postImmediateRefresh_App(void) {

- SDL_Event ev = { .type = SDL_USEREVENT };

- ev.user.code = immediateRefresh_UserEventCode;

- SDL_PushEvent(&ev);

-}

-

void postCommand_Root(iRoot *d, const char *command) {

 iAssert(command);

 if (strlen(command) == 0) {

@@ -1546,7 +1580,7 @@ void postCommandf_App(const char *command, ...) {

}



void rootOrder_App(iRoot *roots[2]) {

- const iWindow *win = as_Window(app_.window);

+ const iWindow *win = get_Window();

 roots[0] = win->keyRoot;

 roots[1] = (roots[0] == win->roots[0] ? win->roots[1] : win->roots[0]);

}

@@ -1583,6 +1617,16 @@ void removeTicker_App(iTickerFunc ticker, iAny *context) {

 remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker });

}



+void addPopup_App(iWindow *popup) {

+ iApp *d = &app_;

+ pushBack_PtrArray(&d->popupWindows, popup);

+}

+

+void removePopup_App(iWindow *popup) {

+ iApp *d = &app_;

+ removeOne_PtrArray(&d->popupWindows, popup);

+}

+

iMimeHooks *mimeHooks_App(void) {

 return app_.mimehooks;

}

@@ -1836,8 +1880,10 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe

static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {

 iApp *d = &app_;

 if (equal_Command(cmd, "ident.showmore")) {

- iForEach(ObjectList, i,

- children_Widget(findChild_Widget(dlg, isUsingPanelLayout_Mobile() ? "panel.top" : "headings"))) {

+ iForEach(ObjectList,

+ i,

+ children_Widget(findChild_Widget(

+ dlg, isUsingPanelLayout_Mobile() ? "panel.top" : "headings"))) {

         if (flags_Widget(i.object) & collapse_WidgetFlag) {

             setFlags_Widget(i.object, hidden_WidgetFlag, iFalse);

         }

@@ -1978,9 +2024,15 @@ const iString *searchQueryUrl_App(const iString *queryStringUnescaped) {

 return collectNewFormat_String("%s?%s", cstr_String(&d->prefs.searchUrl), cstr_String(escaped));

}



+static void resetFonts_App_(iApp *d) {

+ iConstForEach(PtrArray, win, listWindows_App_(d)) {

+ resetFonts_Text(text_Window(win.ptr));

+ } 

+}

+

iBool handleCommand_App(const char *cmd) {

 iApp *d = &app_;

- const iBool isFrozen = !d->window || d->window->base.isDrawFrozen;

+ const iBool isFrozen = !d->window || d->window->isDrawFrozen;

 if (equal_Command(cmd, "config.error")) {

     makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR",

                              format_CStr("Error in config file: %s\n"

@@ -2047,18 +2099,18 @@ iBool handleCommand_App(const char *cmd) {

     return iTrue;

 }

 else if (equal_Command(cmd, "font.reset")) {

- resetFonts_Text();

+ resetFonts_App_(d);

     return iTrue;

 }

 else if (equal_Command(cmd, "font.user")) {

     const char *path = suffixPtr_Command(cmd, "path");

     if (cmp_String(&d->prefs.symbolFontPath, path)) {

         if (!isFrozen) {

- setFreezeDraw_Window(get_Window(), iTrue);

+ setFreezeDraw_MainWindow(get_MainWindow(), iTrue);

         }

         setCStr_String(&d->prefs.symbolFontPath, path);

         loadUserFonts_Text();

- resetFonts_Text();

+ resetFonts_App_(d);

         if (!isFrozen) {

             postCommand_App("font.changed");

             postCommand_App("window.unfreeze");

@@ -2068,10 +2120,10 @@ iBool handleCommand_App(const char *cmd) {

 }

 else if (equal_Command(cmd, "font.set")) {

     if (!isFrozen) {

- setFreezeDraw_Window(get_Window(), iTrue);

+ setFreezeDraw_MainWindow(get_MainWindow(), iTrue);

     }

     d->prefs.font = arg_Command(cmd);

- setContentFont_Text(d->prefs.font);

+ setContentFont_Text(text_Window(d->window), d->prefs.font);

     if (!isFrozen) {

         postCommand_App("font.changed");

         postCommand_App("window.unfreeze");

@@ -2080,10 +2132,10 @@ iBool handleCommand_App(const char *cmd) {

 }

 else if (equal_Command(cmd, "headingfont.set")) {

     if (!isFrozen) {

- setFreezeDraw_Window(get_Window(), iTrue);

+ setFreezeDraw_MainWindow(get_MainWindow(), iTrue);

     }

     d->prefs.headingFont = arg_Command(cmd);

- setHeadingFont_Text(d->prefs.headingFont);

+ setHeadingFont_Text(text_Window(d->window), d->prefs.headingFont);

     if (!isFrozen) {

         postCommand_App("font.changed");

         postCommand_App("window.unfreeze");

@@ -2092,10 +2144,10 @@ iBool handleCommand_App(const char *cmd) {

 }

 else if (equal_Command(cmd, "zoom.set")) {

     if (!isFrozen) {

- setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */

+ setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */

     }

     d->prefs.zoomPercent = arg_Command(cmd);

- setContentFontSize_Text((float) d->prefs.zoomPercent / 100.0f);

+ setContentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f);

     if (!isFrozen) {

         postCommand_App("font.changed");

         postCommand_App("window.unfreeze");

@@ -2104,14 +2156,14 @@ iBool handleCommand_App(const char *cmd) {

 }

 else if (equal_Command(cmd, "zoom.delta")) {

     if (!isFrozen) {

- setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */

+ setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */

     }

     int delta = arg_Command(cmd);

     if (d->prefs.zoomPercent < 100 || (delta < 0 && d->prefs.zoomPercent == 100)) {

         delta /= 2;

     }

     d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200);

- setContentFontSize_Text((float) d->prefs.zoomPercent / 100.0f);

+ setContentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f);

     if (!isFrozen) {

         postCommand_App("font.changed");

         postCommand_App("window.unfreeze");

@@ -2211,7 +2263,7 @@ iBool handleCommand_App(const char *cmd) {

          equal_Command(cmd, "prefs.mono.gopher.changed")) {

     const iBool isSet = (arg_Command(cmd) != 0);

     if (!isFrozen) {

- setFreezeDraw_Window(as_Window(d->window), iTrue);

+ setFreezeDraw_MainWindow(get_MainWindow(), iTrue);

     }

     if (startsWith_CStr(cmd, "prefs.mono.gemini")) {

         d->prefs.monospaceGemini = isSet;

@@ -2936,3 +2988,7 @@ iStringSet *listOpenURLs_App(void) {

 iRelease(docs);

 return set;

}

+

+iMainWindow *mainWindow_App(void) {

+ return app_.window;

+}

diff --git a/src/app.h b/src/app.h

index 08589000..8966e8c7 100644

--- a/src/app.h

+++ b/src/app.h

@@ -22,8 +22,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */



#pragma once



-/* Application core: event loop, base event processing, audio synth. */

-

#include <the_Foundation/objectlist.h>

#include <the_Foundation/string.h>

#include <the_Foundation/stringset.h>

@@ -35,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

iDeclareType(Bookmarks)

iDeclareType(DocumentWidget)

iDeclareType(GmCerts)

+iDeclareType(MainWindow)

iDeclareType(MimeHooks)

iDeclareType(Periodic)

iDeclareType(Root)

@@ -61,14 +60,12 @@ enum iAppEventMode {

enum iUserEventCode {

 command_UserEventCode = 1,

 refresh_UserEventCode,

- arrange_UserEventCode,

 asleep_UserEventCode,

 /* The start of a potential touch tap event is notified via a custom event because

    sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will

    take, it could turn into a tap-and-hold for example. */

 widgetTapBegins_UserEventCode,

 widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */

- immediateRefresh_UserEventCode, /* refresh even though more events are pending */

};



const iString *execPath_App (void);

@@ -119,8 +116,9 @@ iAny * findWidget_App (const char *id);

void addTicker_App (iTickerFunc ticker, iAny *context);

void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context);

void removeTicker_App (iTickerFunc ticker, iAny *context);

+void addPopup_App (iWindow *popup);

+void removePopup_App (iWindow *popup);

void postRefresh_App (void);

-void postImmediateRefresh_App(void);

void postCommand_Root (iRoot *, const char *command);

void postCommandf_Root (iRoot *, const char *command, ...);

void postCommandf_App (const char *command, ...);

@@ -138,3 +136,5 @@ iDocumentWidget * document_Command (const char *cmd);



void openInDefaultBrowser_App (const iString *url);

void revealPath_App (const iString *path);

+

+iMainWindow *mainWindow_App(void);

diff --git a/src/ios.m b/src/ios.m

index 3fb0af48..b46fb8dc 100644

--- a/src/ios.m

+++ b/src/ios.m

@@ -247,14 +247,14 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {

 UIView *view         = [viewController_(get_Window()) view];

 CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil];

// NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame));

- iWindow *window = get_Window();

- const iInt2 rootSize = size_Root(window->roots[0]);

- const int keyTop = keyboardFrame.origin.y * window->pixelRatio;

- setKeyboardHeight_Window(window, rootSize.y - keyTop);

+ iMainWindow *window = get_MainWindow();

+ const iInt2 rootSize = size_Root(window->base.roots[0]);

+ const int keyTop = keyboardFrame.origin.y * window->base.pixelRatio;

+ setKeyboardHeight_MainWindow(window, rootSize.y - keyTop);

}



-(void)keyboardOffScreen:(NSNotification *)notification {

- setKeyboardHeight_Window(get_Window(), 0);

+ setKeyboardHeight_MainWindow(get_MainWindow(), 0);

}

@end



diff --git a/src/macos.h b/src/macos.h

index 0d3f097a..20b95943 100644

--- a/src/macos.h

+++ b/src/macos.h

@@ -24,6 +24,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */



#include "ui/util.h"



+iDeclareType(Window)

+

/* Platform-specific functionality for macOS */



iBool shouldDefaultToMetalRenderer_MacOS (void);

@@ -31,6 +33,7 @@ iBool shouldDefaultToMetalRenderer_MacOS (void);

void enableMomentumScroll_MacOS (void);

void registerURLHandler_MacOS (void);

void setupApplication_MacOS (void);

+void hideTitleBar_MacOS (iWindow *window);

void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count);

void removeMenu_MacOS (int atIndex);

void enableMenu_MacOS (const char *menuLabel, iBool enable);

diff --git a/src/macos.m b/src/macos.m

index d588fa4a..298db0f8 100644

--- a/src/macos.m

+++ b/src/macos.m

@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

#include "ui/window.h"



#include <SDL_timer.h>

+#include <SDL_syswm.h>



#import <AppKit/AppKit.h>



@@ -51,6 +52,16 @@ static iInt2 macVer_(void) {

 return init_I2(10, 10);

}



+static NSWindow *nsWindow_(SDL_Window *window) {

+ SDL_SysWMinfo wm;

+ SDL_VERSION(&wm.version);

+ if (SDL_GetWindowWMInfo(window, &wm)) {

+ return wm.info.cocoa.window;

+ }

+ iAssert(false);

+ return nil;

+}

+

static NSString *currentSystemAppearance_(void) {

 /* This API does not exist on 10.13. */

 if ([NSApp respondsToSelector:@selector(effectiveAppearance)]) {

@@ -370,6 +381,11 @@ void setupApplication_MacOS(void) {

 windowCloseItem.action = @selector(closeTab);

}



+void hideTitleBar_MacOS(iWindow *window) {

+ NSWindow *w = nsWindow_(window->win);

+ w.styleMask = 0; /* borderless */

+}

+

void enableMenu_MacOS(const char *menuLabel, iBool enable) {

 menuLabel = translateCStr_Lang(menuLabel);

 NSApplication *app = [NSApplication sharedApplication];

@@ -377,7 +393,6 @@ void enableMenu_MacOS(const char *menuLabel, iBool enable) {

 NSString *label = [NSString stringWithUTF8String:menuLabel];

 NSMenuItem *menuItem = [appMenu itemAtIndex:[appMenu indexOfItemWithTitle:label]];

 [menuItem setEnabled:enable];

- [label release];

}



void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) {

diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c

index ed9e41d6..6f9824de 100644

--- a/src/ui/documentwidget.c

+++ b/src/ui/documentwidget.c

@@ -750,7 +750,7 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) {

 if (document_App() != d) {

     return 0;

 }

- if (get_Window()->isDrawFrozen) {

+ if (as_MainWindow(window_Widget(d))->isDrawFrozen) {

     return 0;

 }

 static const uint32_t invalidInterval_ = ~0u;

diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c

index a561d5bd..f02bf408 100644

--- a/src/ui/inputwidget.c

+++ b/src/ui/inputwidget.c

@@ -2352,7 +2352,7 @@ static void draw_InputWidget_(const iInputWidget *d) {

 }

 /* Draw the insertion point. */

 if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) &&

- isEmpty_Range(&d->mark)) {

+ (deviceType_App() == desktop_AppDeviceType || isEmpty_Range(&d->mark))) {

     iInt2    curSize;

     iRangecc cursorChar    = iNullRange;

     int      visWrapsAbove = 0;

diff --git a/src/ui/root.c b/src/ui/root.c

index 52a08eca..9e290b05 100644

--- a/src/ui/root.c

+++ b/src/ui/root.c

@@ -298,16 +298,6 @@ void destroyPending_Root(iRoot *d) {

 setCurrent_Root(oldRoot);

}



-void postArrange_Root(iRoot *d) {

- if (!d->pendingArrange) {

- d->pendingArrange = iTrue;

- SDL_Event ev = { .type = SDL_USEREVENT };

- ev.user.code = arrange_UserEventCode;

- ev.user.data2 = d;

- SDL_PushEvent(&ev);

- }

-}

-

iPtrArray *onTop_Root(iRoot *d) {

 if (!d->onTop) {

     d->onTop = new_PtrArray();

diff --git a/src/ui/root.h b/src/ui/root.h

index 740e97c9..04dd5e16 100644

--- a/src/ui/root.h

+++ b/src/ui/root.h

@@ -9,6 +9,7 @@ iDeclareType(Root)



struct Impl_Root {

 iWidget *  widget;

+ iWindow * window;

 iPtrArray *onTop; /* order is important; last one is topmost */

 iPtrSet *  pendingDestruction;

 iBool      pendingArrange;

@@ -29,7 +30,6 @@ iAnyObject *findWidget_Root (const char id); / under curre



iPtrArray * onTop_Root (iRoot *);

void destroyPending_Root (iRoot *);

-void postArrange_Root (iRoot *);



void updateMetrics_Root (iRoot *);

void updatePadding_Root (iRoot ); / TODO: is part of metrics? */

diff --git a/src/ui/text.c b/src/ui/text.c

index f7fff4bc..bf71b0e9 100644

--- a/src/ui/text.c

+++ b/src/ui/text.c

@@ -290,7 +290,9 @@ struct Impl_Text {

 iRegExp *      ansiEscape;

};



-static iText text_;

+iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render)

+

+static iText *activeText_;

static iBlock *userFont_;



static void initFonts_Text_(iText *d) {

@@ -501,8 +503,7 @@ void loadUserFonts_Text(void) {

 }

}



-void init_Text(SDL_Renderer *render) {

- iText *d = &text_;

+void init_Text(iText *d, SDL_Renderer *render) {

 loadUserFonts_Text();

 d->contentFont     = nunito_TextFont;

 d->headingFont     = nunito_TextFont;

@@ -521,8 +522,7 @@ void init_Text(SDL_Renderer *render) {

 initFonts_Text_(d);

}



-void deinit_Text(void) {

- iText *d = &text_;

+void deinit_Text(iText *d) {

 SDL_FreePalette(d->grayscale);

 deinitFonts_Text_(d);

 deinitCache_Text_(d);

@@ -530,30 +530,34 @@ void deinit_Text(void) {

 iRelease(d->ansiEscape);

}



+void setCurrent_Text(iText *d) {

+ activeText_ = d;

+}

+

void setOpacity_Text(float opacity) {

- SDL_SetTextureAlphaMod(text_.cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f);

+ SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f);

}



-void setContentFont_Text(enum iTextFont font) {

- if (text_.contentFont != font) {

- text_.contentFont = font;

- resetFonts_Text();

+void setContentFont_Text(iText *d, enum iTextFont font) {

+ if (d->contentFont != font) {

+ d->contentFont = font;

+ resetFonts_Text(d);

 }

}



-void setHeadingFont_Text(enum iTextFont font) {

- if (text_.headingFont != font) {

- text_.headingFont = font;

- resetFonts_Text();

+void setHeadingFont_Text(iText *d, enum iTextFont font) {

+ if (d->headingFont != font) {

+ d->headingFont = font;

+ resetFonts_Text(d);

 }

}



-void setContentFontSize_Text(float fontSizeFactor) {

+void setContentFontSize_Text(iText *d, float fontSizeFactor) {

 fontSizeFactor *= contentScale_Text_;

 iAssert(fontSizeFactor > 0);

- if (iAbs(text_.contentFontSize - fontSizeFactor) > 0.001f) {

- text_.contentFontSize = fontSizeFactor;

- resetFonts_Text();

+ if (iAbs(d->contentFontSize - fontSizeFactor) > 0.001f) {

+ d->contentFontSize = fontSizeFactor;

+ resetFonts_Text(d);

 }

}



@@ -565,8 +569,7 @@ static void resetCache_Text_(iText *d) {

 initCache_Text_(d);

}



-void resetFonts_Text(void) {

- iText *d = &text_;

+void resetFonts_Text(iText *d) {

 deinitFonts_Text_(d);

 deinitCache_Text_(d);

 initCache_Text_(d);

@@ -574,7 +577,7 @@ void resetFonts_Text(void) {

}



iLocalDef iFont *font_Text_(enum iFontId id) {

- return &text_.fonts[id & mask_FontId];

+ return &activeText_->fonts[id & mask_FontId];

}



static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) {

@@ -584,7 +587,7 @@ static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, fl

 SDL_Surface *surface8 =

     SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8);

 SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE);

- SDL_SetSurfacePalette(surface8, text_.grayscale);

+ SDL_SetSurfacePalette(surface8, activeText_->grayscale);

#if LAGRANGE_RASTER_DEPTH != 8

 /* Convert to the cache format. */

 SDL_Surface *surf = SDL_ConvertSurfaceFormat(surface8, LAGRANGE_RASTER_FORMAT, 0);

@@ -631,7 +634,7 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) {

     &d->font, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1);

 glRect->size = init_I2(x1 - x0, y1 - y0);

 /* Determine placement in the glyph cache texture, advancing in rows. */

- glRect->pos = assignCachePos_Text_(&text_, glRect->size);

+ glRect->pos = assignCachePos_Text_(activeText_, glRect->size);

 glyph->d[hoff] = init_I2(x0, y0);

 glyph->d[hoff].y += d->vertOffset;

 if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */

@@ -737,11 +740,11 @@ static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) {

 }

 else {

     /* If the cache is running out of space, clear it and we'll recache what's needed currently. */

- if (text_.cacheBottom > text_.cacheSize.y - maxGlyphHeight_Text_(&text_)) {

+ if (activeText_->cacheBottom > activeText_->cacheSize.y - maxGlyphHeight_Text_(activeText_)) {

#if !defined (NDEBUG)

         printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout);

#endif

- resetCache_Text_(&text_);

+ resetCache_Text_(activeText_);

     }

     glyph       = new_Glyph(glyphIndex);

     glyph->font = d;

@@ -858,7 +861,7 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i

}



static enum iFontId fontId_Text_(const iFont *font) {

- return (enum iFontId) (font - text_.fonts);

+ return (enum iFontId) (font - activeText_->fonts);

}



static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) {

@@ -949,7 +952,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh

         /* Do a regexp match in the source text. */

         iRegExpMatch m;

         init_RegExpMatch(&m);

- if (match_RegExp(text_.ansiEscape, srcPos, d->source.end - srcPos, &m)) {

+ if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) {

             finishRun_AttributedText_(d, &run, pos - 1);

             run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1),

                                                tmParagraph_ColorId);

@@ -1082,9 +1085,9 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {

 while (index < size_Array(glyphIndices)) {

     for (; index < size_Array(glyphIndices); index++) {

         const uint32_t glyphIndex = constValue_Array(glyphIndices, index, uint32_t);

- const int lastCacheBottom = text_.cacheBottom;

+ const int lastCacheBottom = activeText_->cacheBottom;

         iGlyph *glyph = glyphByIndex_Font_(d, glyphIndex);

- if (text_.cacheBottom < lastCacheBottom) {

+ if (activeText_->cacheBottom < lastCacheBottom) {

             /* The cache was reset due to running out of space. We need to restart from

                the beginning! */

             bufX = 0;

@@ -1103,7 +1106,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {

                             LAGRANGE_RASTER_DEPTH,

                             LAGRANGE_RASTER_FORMAT);

                 SDL_SetSurfaceBlendMode(buf, SDL_BLENDMODE_NONE);

- SDL_SetSurfacePalette(buf, text_.grayscale);

+ SDL_SetSurfacePalette(buf, activeText_->grayscale);

             }

             SDL_Surface *surfaces[2] = {

                 !isRasterized_Glyph_(glyph, 0) ?

@@ -1147,19 +1150,19 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {

     }

     /* Finished or the buffer is full, copy the glyphs to the cache texture. */

     if (!isEmpty_Array(rasters)) {

- SDL_Texture *bufTex = SDL_CreateTextureFromSurface(text_.render, buf);

+ SDL_Texture *bufTex = SDL_CreateTextureFromSurface(activeText_->render, buf);

         SDL_SetTextureBlendMode(bufTex, SDL_BLENDMODE_NONE);

         if (!isTargetChanged) {

             isTargetChanged = iTrue;

- oldTarget = SDL_GetRenderTarget(text_.render);

- SDL_SetRenderTarget(text_.render, text_.cache);

+ oldTarget = SDL_GetRenderTarget(activeText_->render);

+ SDL_SetRenderTarget(activeText_->render, activeText_->cache);

         }

// printf("copying %zu rasters from %p\n", size_Array(rasters), bufTex); fflush(stdout);

         iConstForEach(Array, i, rasters) {

             const iRasterGlyph *rg = i.value;

// iAssert(isEqual_I2(rg->rect.size, rg->glyph->rect[rg->hoff].size));

             const iRect *glRect = &rg->glyph->rect[rg->hoff];

- SDL_RenderCopy(text_.render,

+ SDL_RenderCopy(activeText_->render,

                            bufTex,

                            (const SDL_Rect *) &rg->rect,

                            (const SDL_Rect *) glRect);

@@ -1179,7 +1182,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {

     SDL_FreeSurface(buf);

 }

 if (isTargetChanged) {

- SDL_SetRenderTarget(text_.render, oldTarget);

+ SDL_SetRenderTarget(activeText_->render, oldTarget);

 }

}



@@ -1706,9 +1709,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {

                 }

                 if (~mode & permanentColorFlag_RunMode) {

                     const iColor clr = run->fgColor;

- SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b);

+ SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);

                     if (args->mode & fillBackground_RunMode) {

- SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0);

+ SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);

                     }

                 }

                 SDL_Rect src;

@@ -1719,9 +1722,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {

                     /* Alpha blending looks much better if the RGB components don't change in

                        the partially transparent pixels. */

                     /* TODO: Backgrounds of all glyphs should be cleared before drawing anything else. */

- SDL_RenderFillRect(text_.render, &dst);

+ SDL_RenderFillRect(activeText_->render, &dst);

                 }

- SDL_RenderCopy(text_.render, text_.cache, &src, &dst);

+ SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst);

#if 0

                 /* Show spaces and direction. */

                 if (logicalText[logPos] == 0x20) {

@@ -1863,7 +1866,7 @@ iTextMetrics measureN_Text(int fontId, const char *text, size_t n) {

}



static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) {

- iText * d = &text_;

+ iText * d = activeText_;

 iFont *      font = font_Text_(fontId);

 const iColor clr  = get_Color(color & mask_ColorId);

 SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b);

@@ -2057,7 +2060,7 @@ iTextMetrics draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) {

}



SDL_Texture *glyphCache_Text(void) {

- return text_.cache;

+ return activeText_->cache;

}



static void freeBitmap_(void *ptr) {

@@ -2170,7 +2173,7 @@ iString *renderBlockChars_Text(const iBlock *fontData, int height, enum iTextBlo

iDefineTypeConstructionArgs(TextBuf, (iWrapText *wrapText, int font, int color), wrapText, font, color)



void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) {

- SDL_Renderer *render = text_.render;

+ SDL_Renderer *render = activeText_->render;

 d->size = measure_WrapText(wrapText, font).bounds.size;

 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");

 if (d->size.x * d->size.y) {

@@ -2191,9 +2194,9 @@ void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) {

     SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE);

     SDL_SetRenderDrawColor(render, 255, 255, 255, 0);

     SDL_RenderClear(render);

- SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */

+ SDL_SetTextureBlendMode(activeText_->cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */

     draw_WrapText(wrapText, font, zero_I2(), color | fillBackground_ColorId);

- SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND);

+ SDL_SetTextureBlendMode(activeText_->cache, SDL_BLENDMODE_BLEND);

     SDL_SetRenderTarget(render, oldTarget);

     origin_Paint = oldOrigin;

     SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND);

@@ -2212,7 +2215,7 @@ void draw_TextBuf(const iTextBuf *d, iInt2 pos, int color) {

 addv_I2(&pos, origin_Paint);

 const iColor clr = get_Color(color);

 SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b);

- SDL_RenderCopy(text_.render,

+ SDL_RenderCopy(activeText_->render,

                d->texture,

                &(SDL_Rect){ 0, 0, d->size.x, d->size.y },

                &(SDL_Rect){ pos.x, pos.y, d->size.x, d->size.y });

diff --git a/src/ui/text.h b/src/ui/text.h

index ac6cc1c1..1da43818 100644

--- a/src/ui/text.h

+++ b/src/ui/text.h

@@ -139,15 +139,20 @@ enum iTextFont {



extern int gap_Text; /* affected by content font size */



-void init_Text (SDL_Renderer *);

-void deinit_Text (void);

+iDeclareType(Text)

+iDeclareTypeConstructionArgs(Text, SDL_Renderer *)

+

+void init_Text (iText *, SDL_Renderer *);

+void deinit_Text (iText *);

+

+void setCurrent_Text (iText *);



void loadUserFonts_Text (void); /* based on Prefs */



-void setContentFont_Text (enum iTextFont font);

-void setHeadingFont_Text (enum iTextFont font);

-void setContentFontSize_Text (float fontSizeFactor); /* affects all except default* fonts */

-void resetFonts_Text (void);

+void setContentFont_Text (iText *, enum iTextFont font);

+void setHeadingFont_Text (iText *, enum iTextFont font);

+void setContentFontSize_Text (iText , float fontSizeFactor); / affects all except default* fonts */

+void resetFonts_Text (iText *);



int lineHeight_Text (int fontId);

iRect visualBounds_Text (int fontId, iRangecc text);

diff --git a/src/ui/text_simple.c b/src/ui/text_simple.c

index bf33b4be..8b1de64a 100644

--- a/src/ui/text_simple.c

+++ b/src/ui/text_simple.c

@@ -92,7 +92,7 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {

 }

 if (args->mode & fillBackground_RunMode) {

     const iColor initial = get_Color(args->color);

- SDL_SetRenderDrawColor(text_.render, initial.r, initial.g, initial.b, 0);

+ SDL_SetRenderDrawColor(activeText_->render, initial.r, initial.g, initial.b, 0);

 }

 /* Text rendering is not very straightforward! Let's dive in... */

 iChar       prevCh = 0;

@@ -114,14 +114,14 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {

         chPos++;

         iRegExpMatch m;

         init_RegExpMatch(&m);

- if (match_RegExp(text_.ansiEscape, chPos, args->text.end - chPos, &m)) {

+ if (match_RegExp(activeText_->ansiEscape, chPos, args->text.end - chPos, &m)) {

             if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {

                 /* Change the color. */

                 const iColor clr =

                     ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), tmParagraph_ColorId);

- SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b);

+ SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);

                 if (args->mode & fillBackground_RunMode) {

- SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0);

+ SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);

                 }

             }

             chPos = end_RegExpMatch(&m);

@@ -205,9 +205,9 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {

             }

             if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {

                 const iColor clr = get_Color(colorNum);

- SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b);

+ SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);

                 if (args->mode & fillBackground_RunMode) {

- SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0);

+ SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);

                 }

             }

             prevCh = 0;

@@ -311,9 +311,9 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {

         if (args->mode & fillBackground_RunMode) {

             /* Alpha blending looks much better if the RGB components don't change in

                the partially transparent pixels. */

- SDL_RenderFillRect(text_.render, &dst);

+ SDL_RenderFillRect(activeText_->render, &dst);

         }

- SDL_RenderCopy(text_.render, text_.cache, &src, &dst);

+ SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst);

     }

     xpos += advance;

     if (!isSpace_Char(ch)) {

diff --git a/src/ui/util.c b/src/ui/util.c

index 721aed2d..38977b96 100644

--- a/src/ui/util.c

+++ b/src/ui/util.c

@@ -613,6 +613,8 @@ iBool isAction_Widget(const iWidget *d) {

/-----------------------------------------------------------------------------------------------/



static iBool isCommandIgnoredByMenus_(const char *cmd) {

+ if (equal_Command(cmd, "window.focus.lost") ||

+ equal_Command(cmd, "window.focus.gained")) return iTrue;

 /* TODO: Perhaps a common way of indicating which commands are notifications and should not

    be reacted to by menus? */

 return equal_Command(cmd, "media.updated") ||

@@ -810,6 +812,10 @@ static void updateMenuItemFonts_Widget_(iWidget *d) {

 }

}



+iLocalDef iBool isUsingMenuPopupWindows_(void) {

+ return deviceType_App() == desktop_AppDeviceType;

+}

+

void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {

 const iRect rootRect        = rect_Root(d->root);

 const iInt2 rootSize        = rootRect.size;

@@ -822,6 +828,26 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {

 processEvents_App(postedEventsOnly_AppEventMode);

 setFlags_Widget(d, hidden_WidgetFlag, iFalse);

 setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue);

+ if (isUsingMenuPopupWindows_()) {

+ if (postCommands) {

+ postCommand_Widget(d, "menu.opened");

+ }

+ updateMenuItemFonts_Widget_(d);

+ iRoot *oldRoot = current_Root();

+ setFlags_Widget(d, keepOnTop_WidgetFlag, iFalse);

+ setUserData_Object(d, parent_Widget(d));

+ removeChild_Widget(parent_Widget(d), d); /* we'll borrow the widget for a while */

+ iInt2 mousePos;

+ SDL_GetGlobalMouseState(&mousePos.x, &mousePos.y);

+ iWindow *win = newPopup_Window(sub_I2(mousePos, divi_I2(gap2_UI, 2)), d);

+ SDL_SetWindowTitle(win->win, "Menu");

+ addPopup_App(win); /* window takes the widget */

+ SDL_ShowWindow(win->win);

+ draw_Window(win);

+ setCurrent_Window(mainWindow_App());

+ setCurrent_Root(oldRoot);

+ return;

+ }

 raise_Widget(d);

 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse);

 if (isPortraitPhone) {

@@ -836,7 +862,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {

 arrange_Widget(d);

 if (isPortraitPhone) {

     if (isSlidePanel) {

- d->rect.pos = zero_I2(); //neg_I2(bounds_Widget(parent_Widget(d)).pos);

+ d->rect.pos = zero_I2();

     }

     else {

         d->rect.pos = init_I2(0, rootSize.y);

@@ -856,7 +882,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {

     float l, t, r, b;

     safeAreaInsets_iOS(&l, &t, &r, &b);

     topExcess    += t;

- bottomExcess += iMax(b, get_Window()->keyboardHeight);

+ bottomExcess += iMax(b, get_MainWindow()->keyboardHeight);

     leftExcess   += l;

     rightExcess  += r;

 }

@@ -884,6 +910,18 @@ void closeMenu_Widget(iWidget *d) {

 if (d == NULL || flags_Widget(d) & hidden_WidgetFlag) {

     return; /* Already closed. */

 }

+ if (isUsingMenuPopupWindows_()) {

+ iWindow *win = window_Widget(d);

+ iAssert(type_Window(win) == popup_WindowType);

+ iWidget *originalParent = userData_Object(d);

+ setUserData_Object(d, NULL);

+ win->roots[0]->widget = NULL;

+ setRoot_Widget(d, originalParent->root);

+ addChild_Widget(originalParent, d);

+ setFlags_Widget(d, keepOnTop_WidgetFlag, iTrue);

+ SDL_HideWindow(win->win);

+ collect_Garbage(win, (iDeleteFunc) delete_Window); /* get rid of it after event processing */

+ }

 setFlags_Widget(d, hidden_WidgetFlag, iTrue);

 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue);

 postRefresh_App();

diff --git a/src/ui/widget.c b/src/ui/widget.c

index 23c19315..7b33a752 100644

--- a/src/ui/widget.c

+++ b/src/ui/widget.c

@@ -271,6 +271,10 @@ iWidget *root_Widget(const iWidget *d) {

 return d ? d->root->widget : NULL;

}



+iWindow *window_Widget(const iAnyObject *d) {

+ return constAs_Widget(d)->root->window;

+}

+

void showCollapsed_Widget(iWidget *d, iBool show) {

 const iBool isVisible = !(d->flags & hidden_WidgetFlag);

 if ((isVisible && !show) || (!isVisible && show)) {

@@ -979,11 +983,10 @@ void unhover_Widget(void) {

}



iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {

- //iAssert(d->root == get_Root());

 if (!d->parent) {

- if (get_Window()->focus && get_Window()->focus->root == d->root && isKeyboardEvent_(ev)) {

+ if (window_Widget(d)->focus && window_Widget(d)->focus->root == d->root && isKeyboardEvent_(ev)) {

         /* Root dispatches keyboard events directly to the focused widget. */

- if (dispatchEvent_Widget(get_Window()->focus, ev)) {

+ if (dispatchEvent_Widget(window_Widget(d)->focus, ev)) {

             return iTrue;

         }

     }

@@ -1012,7 +1015,8 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {

     }

 }

 else if (ev->type == SDL_MOUSEMOTION &&

- (!get_Window()->hover || hasParent_Widget(d, get_Window()->hover)) &&

+ ev->motion.windowID == SDL_GetWindowID(window_Widget(d)->win) &&

+ (!window_Widget(d)->hover || hasParent_Widget(d, window_Widget(d)->hover)) &&

          flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag &&

          ~flags_Widget(d) & disabled_WidgetFlag) {

     if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) {

@@ -1031,11 +1035,11 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {

     iReverseForEach(ObjectList, i, d->children) {

         iWidget *child = as_Widget(i.object);

         //iAssert(child->root == d->root);

- if (child == get_Window()->focus && isKeyboardEvent_(ev)) {

+ if (child == window_Widget(d)->focus && isKeyboardEvent_(ev)) {

             continue; /* Already dispatched. */

         }

         if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) {

- /* Already dispatched. */

+ /* Already dispatched. */

             continue;

         }

         if (dispatchEvent_Widget(child, ev)) {

@@ -1050,7 +1054,7 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {

#endif

#if 0

             if (ev->type == SDL_MOUSEMOTION) {

- printf("[%p] %s:'%s' (on top) ate the motion\n",

+ printf("[%p] %s:'%s' ate the motion\n",

                        child, class_Widget(child)->name,

                        cstr_String(id_Widget(child)));

                 fflush(stdout);

@@ -1246,7 +1250,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {

                            ev->button.x,

                            ev->button.y);

     }

- setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW);

+ setCursor_Window(window_Widget(d), SDL_SYSTEM_CURSOR_ARROW);

     return iTrue;

 }

 return iFalse;

@@ -1270,6 +1274,7 @@ iLocalDef iBool isDrawn_Widget_(const iWidget *d) {

void drawLayerEffects_Widget(const iWidget *d) {

 /* Layered effects are not buffered, so they are drawn here separately. */

 iAssert(isDrawn_Widget_(d));

+ iAssert(window_Widget(d) == get_Window());

 iBool shadowBorder   = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0;

 iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag;

 if (deviceType_App() == phone_AppDeviceType) {

@@ -1539,6 +1544,7 @@ static void endBufferDraw_Widget_(const iWidget *d) {

}



void draw_Widget(const iWidget *d) {

+ iAssert(window_Widget(d) == get_Window());

 if (!isDrawn_Widget_(d)) {

     if (d->drawBuf) {

// printf("[%p] drawBuffer released\n", d);

@@ -1820,7 +1826,17 @@ iBool equalWidget_Command(const char *cmd, const iWidget *widget, const char *ch

 if (equal_Command(cmd, checkCommand)) {

     const iWidget *src = pointer_Command(cmd);

     iAssert(!src || strstr(cmd, " ptr:"));

- return src == widget || hasParent_Widget(src, widget);

+ if (src == widget || hasParent_Widget(src, widget)) {

+ return iTrue;

+ }

+// if (src && type_Window(window_Widget(src)) == popup_WindowType) {

+// /* Special case: command was emitted from a popup widget. The popup root widget actually

+// belongs to someone else. */

+// iWidget *realParent = userData_Object(src->root->widget);

+// iAssert(realParent);

+// iAssert(isInstance_Object(realParent, &Class_Widget));

+// return realParent == widget || hasParent_Widget(realParent, widget);

+// }

 }

 return iFalse;

}

@@ -1962,6 +1978,10 @@ void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) {

 }

 if (!isGlobal) {

     iAssert(isInstance_Object(d, &Class_Widget));

+ if (type_Window(window_Widget(d)) == popup_WindowType) {

+ postCommandf_Root(((const iWidget *) d)->root, "cancel popup:1 ptr:%p", d);

+ d = userData_Object(root_Widget(d));

+ }

     appendFormat_String(&str, " ptr:%p", d);

 }

 postCommandString_Root(((const iWidget *) d)->root, &str);

diff --git a/src/ui/widget.h b/src/ui/widget.h

index 7491cb79..0eab69c1 100644

--- a/src/ui/widget.h

+++ b/src/ui/widget.h

@@ -34,7 +34,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

#include <the_Foundation/string.h>

#include <SDL_events.h>



-iDeclareType(Root) /* each widget is associated with a Root */

+iDeclareType(Root) /* each widget is associated with a Root */

+iDeclareType(Window) /* each Root is inside a Window */



#define iDeclareWidgetClass(className) \

 iDeclareType(className); \

@@ -185,6 +186,7 @@ void releaseChildren_Widget (iWidget *);

 - inner:  0,0 is at the top left corner of the widget */



iWidget * root_Widget (const iWidget *);

+iWindow * window_Widget (const iAnyObject *);

const iString * id_Widget (const iWidget *);

int64_t flags_Widget (const iWidget *);

iRect bounds_Widget (const iWidget ); / outer bounds */

diff --git a/src/ui/window.c b/src/ui/window.c

index 92125d81..e9a34ace 100644

--- a/src/ui/window.c

+++ b/src/ui/window.c

@@ -57,7 +57,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

#include "stb_image.h"

#include "stb_image_resize.h"



-static iWindow *theWindow_ = NULL;

+static iWindow * theWindow_;

+static iMainWindow *theMainWindow_;



#if defined (iPlatformApple) || defined (iPlatformLinux) || defined (iPlatformOther)

static float initialUiScale_ = 1.0f;

@@ -67,6 +68,9 @@ static float initialUiScale_ = 1.1f;



static iBool isOpenGLRenderer_;



+iDefineTypeConstructionArgs(Window,

+ (enum iWindowType type, iRect rect, uint32_t flags),

+ type, rect, flags)

iDefineTypeConstructionArgs(MainWindow, (iRect rect), rect)



/* TODO: Define menus per platform. */

@@ -205,6 +209,7 @@ static void setupUserInterface_MainWindow(iMainWindow *d) {

#endif

 /* One root is created by default. */

 d->base.roots[0] = new_Root();

+ d->base.roots[0]->window = as_Window(d);

 setCurrent_Root(d->base.roots[0]);

 createUserInterface_Root(d->base.roots[0]);

 setCurrent_Root(NULL);

@@ -409,7 +414,6 @@ void init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags)

 d->mouseGrab     = NULL;

 d->focus         = NULL;

 d->pendingCursor = NULL;

- d->isDrawFrozen = iTrue;

 d->isExposed     = iFalse;

 d->isMinimized   = iFalse;

 d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */

@@ -441,9 +445,27 @@ void init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags)

 d->uiScale      = initialUiScale_;

 /* TODO: Ratios, scales, and metrics must be window-specific, not global. */

 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale);

+ d->text = new_Text(d->render);

+}

+

+static void deinitRoots_Window_(iWindow *d) {

+ iRecycle();

+ iForIndices(i, d->roots) {

+ if (d->roots[i]) {

+ setCurrent_Root(d->roots[i]);

+ delete_Root(d->roots[i]);

+ d->roots[i] = NULL;

+ }

+ }

+ setCurrent_Root(NULL);

}



void deinit_Window(iWindow *d) {

+ if (d->type == popup_WindowType) {

+ removePopup_App(d);

+ }

+ deinitRoots_Window_(d);

+ delete_Text(d->text);

 SDL_DestroyRenderer(d->render);

 SDL_DestroyWindow(d->win);

 iForIndices(i, d->cursors) {

@@ -455,6 +477,7 @@ void deinit_Window(iWindow *d) {



void init_MainWindow(iMainWindow *d, iRect rect) {

 theWindow_ = &d->base;

+ theMainWindow_ = d;

 uint32_t flags = 0;

#if defined (iPlatformAppleDesktop)

 SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl");

@@ -465,13 +488,15 @@ void init_MainWindow(iMainWindow *d, iRect rect) {

#endif

 SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");

 init_Window(&d->base, main_WindowType, rect, flags);

- d->splitMode = d->pendingSplitMode = 0;

- d->pendingSplitUrl = new_String();

- d->place.initialPos = rect.pos;

- d->place.normalRect = rect;

+ d->isDrawFrozen = iTrue;

+ d->splitMode = 0;

+ d->pendingSplitMode = 0;

+ d->pendingSplitUrl = new_String();

+ d->place.initialPos = rect.pos;

+ d->place.normalRect = rect;

 d->place.lastNotifiedSize = zero_I2();

- d->place.snap = 0;

- d->keyboardHeight = 0;

+ d->place.snap = 0;

+ d->keyboardHeight = 0;

#if defined(iPlatformMobile)

 const iInt2 minSize = zero_I2(); /* windows aren't independently resizable */

#else

@@ -510,9 +535,9 @@ void init_MainWindow(iMainWindow *d, iRect rect) {

 }

#endif

#if defined (iPlatformAppleMobile)

- setupWindow_iOS(d);

+ setupWindow_iOS(as_Window(d));

#endif

- init_Text(d->base.render);

+ setCurrent_Text(d->base.text);

 SDL_GetRendererOutputSize(d->base.render, &d->base.size.x, &d->base.size.y);    

 setupUserInterface_MainWindow(d);

 postCommand_App("~bindings.changed"); /* update from bindings */

@@ -538,24 +563,15 @@ void init_MainWindow(iMainWindow *d, iRect rect) {

#endif

}



-static void deinitRoots_Window_(iWindow *d) {

- iRecycle();

- iForIndices(i, d->roots) {

- if (d->roots[i]) {

- setCurrent_Root(d->roots[i]);

- deinit_Root(d->roots[i]);

- }

- }

- setCurrent_Root(NULL);

-}

-

void deinit_MainWindow(iMainWindow *d) {

 deinitRoots_Window_(as_Window(d));

 if (theWindow_ == as_Window(d)) {

     theWindow_ = NULL;

 }

+ if (theMainWindow_ == d) {

+ theMainWindow_ = NULL;

+ }

 delete_String(d->pendingSplitUrl);

- deinit_Text();

 deinit_Window(&d->base);

}



@@ -592,7 +608,7 @@ iRoot *otherRoot_Window(const iWindow *d, iRoot *root) {

static void invalidate_MainWindow_(iMainWindow *d, iBool forced) {

 if (d && (!d->base.isInvalidated || forced)) {

     d->base.isInvalidated = iTrue;

- resetFonts_Text();

+ resetFonts_Text(text_Window(d));

     postCommand_App("theme.changed auto:1"); /* forces UI invalidation */

 }

}

@@ -607,7 +623,7 @@ void invalidate_Window(iAnyWindow *d) {

}



static iBool isNormalPlacement_MainWindow_(const iMainWindow *d) {

- if (d->base.isDrawFrozen) return iFalse;

+ if (d->isDrawFrozen) return iFalse;

#if defined (iPlatformApple)

 /* Maximized mode is not special on macOS. */

 if (snap_MainWindow(d) == maximized_WindowSnap) {

@@ -655,7 +671,7 @@ static iBool unsnap_MainWindow_(iMainWindow *d, const iInt2 *newPos) {

static void notifyMetricsChange_Window_(const iWindow *d) {

 /* Dynamic UI metrics change. Widgets need to update themselves. */

 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale);

- resetFonts_Text();

+ resetFonts_Text(d->text);

 postCommand_App("metrics.changed");

}



@@ -676,6 +692,41 @@ static void checkPixelRatioChange_Window_(iWindow *d) {

 }

}



+static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {

+ if (ev->windowID != SDL_GetWindowID(d->win)) {

+ return iFalse;

+ }

+ switch (ev->event) {

+ case SDL_WINDOWEVENT_EXPOSED:

+ d->isExposed = iTrue;

+ postRefresh_App();

+ return iTrue;

+ case SDL_WINDOWEVENT_RESTORED:

+ case SDL_WINDOWEVENT_SHOWN:

+ postRefresh_App();

+ return iTrue;

+ case SDL_WINDOWEVENT_FOCUS_LOST:

+ /* Popup windows are currently only used for menus. */

+ closeMenu_Widget(d->roots[0]->widget);

+ return iTrue;

+ case SDL_WINDOWEVENT_LEAVE:

+ unhover_Widget();

+ d->isMouseInside = iFalse;

+ //postCommand_App("window.mouse.exited");

+// SDL_SetWindowInputFocus(mainWindow_App()->base.win);

+ printf("mouse leaves popup\n"); fflush(stdout);

+ //SDL_RaiseWindow(mainWindow_App()->base.win);

+ postRefresh_App();

+ return iTrue;

+ case SDL_WINDOWEVENT_ENTER:

+ d->isMouseInside = iTrue;

+ //postCommand_App("window.mouse.entered");

+ printf("mouse enters popup\n"); fflush(stdout);

+ return iTrue;

+ }

+ return iFalse;

+}

+

static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) {

 switch (ev->event) {

#if defined(iPlatformDesktop)

@@ -795,6 +846,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent

         return iTrue;

     case SDL_WINDOWEVENT_ENTER:

         d->base.isMouseInside = iTrue;

+ SDL_SetWindowInputFocus(d->base.win);

         postCommand_App("window.mouse.entered");

         return iTrue;

     case SDL_WINDOWEVENT_FOCUS_GAINED:

@@ -802,16 +854,16 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent

         setCapsLockDown_Keys(iFalse);

         postCommand_App("window.focus.gained");

         d->base.isExposed = iTrue;

-#if !defined(iPlatformDesktop)

+#if !defined (iPlatformDesktop)

         /* Returned to foreground, may have lost buffered content. */

- invalidate_Window_(d, iTrue);

+ invalidate_MainWindow_(d, iTrue);

         postCommand_App("window.unfreeze");

#endif

         return iFalse;

     case SDL_WINDOWEVENT_FOCUS_LOST:

         postCommand_App("window.focus.lost");

-#if !defined(iPlatformDesktop)

- setFreezeDraw_Window(d, iTrue);

+#if !defined (iPlatformDesktop)

+ setFreezeDraw_MainWindow(d, iTrue);

#endif

         return iFalse;

     case SDL_WINDOWEVENT_TAKE_FOCUS:

@@ -831,8 +883,8 @@ static void applyCursor_Window_(iWindow *d) {

 }

}



-iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {

- iWindow *w = as_Window(d);

+iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {

+ iMainWindow *mw = (type_Window(d) == main_WindowType ? as_MainWindow(d) : NULL);

 switch (ev->type) {

#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)

     case SDL_SYSWMEVENT: {

@@ -845,19 +897,26 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {

     }

#endif

     case SDL_WINDOWEVENT: {

- return handleWindowEvent_MainWindow_(d, &ev->window);

+ if (mw) {

+ return handleWindowEvent_MainWindow_(mw, &ev->window);

+ }

+ else {

+ return handleWindowEvent_Window_(d, &ev->window);

+ }

     }

     case SDL_RENDER_TARGETS_RESET:

     case SDL_RENDER_DEVICE_RESET: {

- invalidate_MainWindow_(d, iTrue /* force full reset */);

+ if (mw) {

+ invalidate_MainWindow_(mw, iTrue /* force full reset */);

+ }

         break;

     }

     default: {

         SDL_Event event = *ev;

         if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze")) {

- d->base.isDrawFrozen = iFalse;

- if (SDL_GetWindowFlags(w->win) & SDL_WINDOW_HIDDEN) {

- SDL_ShowWindow(w->win);

+ mw->isDrawFrozen = iFalse;

+ if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) {

+ SDL_ShowWindow(d->win);

             }

             postRefresh_App();

             postCommand_App("media.player.update"); /* in case a player needs updating */

@@ -866,35 +925,35 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {

         if (processEvent_Touch(&event)) {

             return iTrue;

         }

- if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->base.focusGainedAt < 10) {

+ if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->focusGainedAt < 10) {

             /* Suspiciously close to when input focus was received. For example under openbox,

                closing xterm with Ctrl+D will cause the keydown event to "spill" over to us.

                As a workaround, ignore these events. */

             return iTrue; /* won't go to bindings, either */

         }

- if (event.type == SDL_MOUSEBUTTONDOWN && d->base.ignoreClick) {

- d->base.ignoreClick = iFalse;

+ if (event.type == SDL_MOUSEBUTTONDOWN && d->ignoreClick) {

+ d->ignoreClick = iFalse;

             return iTrue;

         }

         /* Map mouse pointer coordinate to our coordinate system. */

         if (event.type == SDL_MOUSEMOTION) {

- setCursor_Window(w, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */

- const iInt2 pos = coord_Window(w, event.motion.x, event.motion.y);

+ setCursor_Window(d, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */

+ const iInt2 pos = coord_Window(d, event.motion.x, event.motion.y);

             event.motion.x = pos.x;

             event.motion.y = pos.y;

         }

         else if (event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {

- const iInt2 pos = coord_Window(w, event.button.x, event.button.y);

+ const iInt2 pos = coord_Window(d, event.button.x, event.button.y);

             event.button.x = pos.x;

             event.button.y = pos.y;

             if (event.type == SDL_MOUSEBUTTONDOWN) {

                 /* Button clicks will change keyroot. */

- if (numRoots_Window(w) > 1) {

+ if (numRoots_Window(d) > 1) {

                     const iInt2 click = init_I2(event.button.x, event.button.y);

- iForIndices(i, w->roots) {

- iRoot *root = w->roots[i];

- if (root != w->keyRoot && contains_Rect(rect_Root(root), click)) {

- setKeyRoot_Window(w, root);

+ iForIndices(i, d->roots) {

+ iRoot *root = d->roots[i];

+ if (root != d->keyRoot && contains_Rect(rect_Root(root), click)) {

+ setKeyRoot_Window(d, root);

                             break;

                         }

                     }

@@ -909,13 +968,13 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {

             event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {

             if (mouseGrab_Widget()) {

                 iWidget *grabbed = mouseGrab_Widget();

- setCurrent_Root(findRoot_Window(w, grabbed));

+ setCurrent_Root(findRoot_Window(d, grabbed));

                 wasUsed = dispatchEvent_Widget(grabbed, &event);

             }

         }

         /* Dispatch the event to the tree of widgets. */

         if (!wasUsed) {

- wasUsed = dispatchEvent_Window(w, &event);

+ wasUsed = dispatchEvent_Window(d, &event);

         }

         if (!wasUsed) {

             /* As a special case, clicking the middle mouse button can be used for pasting

@@ -928,35 +987,35 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {

                 paste.key.keysym.mod = KMOD_PRIMARY;

                 paste.key.state      = SDL_PRESSED;

                 paste.key.timestamp  = SDL_GetTicks();

- wasUsed = dispatchEvent_Window(w, &paste);

+ wasUsed = dispatchEvent_Window(d, &paste);

             }

             if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) {

- if (postContextClick_Window(w, &event.button)) {

+ if (postContextClick_Window(d, &event.button)) {

                     wasUsed = iTrue;

                 }

             }

         }

         if (isMetricsChange_UserEvent(&event)) {

- iForIndices(i, w->roots) {

- updateMetrics_Root(w->roots[i]);

+ iForIndices(i, d->roots) {

+ updateMetrics_Root(d->roots[i]);

             }

         }

- if (isCommand_UserEvent(&event, "lang.changed")) {

+ if (isCommand_UserEvent(&event, "lang.changed") && mw) {

#if defined (iHaveNativeMenus)

             /* Retranslate the menus. */

             removeMacMenus_();

             insertMacMenus_();

#endif

- invalidate_Window(w);

- iForIndices(i, w->roots) {

- if (w->roots[i]) {

- updatePreferencesLayout_Widget(findChild_Widget(w->roots[i]->widget, "prefs"));

- arrange_Widget(w->roots[i]->widget);

+ invalidate_Window(d);

+ iForIndices(i, d->roots) {

+ if (d->roots[i]) {

+ updatePreferencesLayout_Widget(findChild_Widget(d->roots[i]->widget, "prefs"));

+ arrange_Widget(d->roots[i]->widget);

                 }

             }

         }

         if (event.type == SDL_MOUSEMOTION) {

- applyCursor_Window_(w);

+ applyCursor_Window_(d);

         }

         return wasUsed;

     }

@@ -1003,6 +1062,9 @@ iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) {

                                                          coord_MouseWheelEvent(&ev->wheel))) {

             continue; /* Only process the event in the relevant split. */

         }

+ if (!root->widget) {

+ continue;

+ }

         setCurrent_Root(root);

         const iBool wasUsed = dispatchEvent_Widget(root->widget, ev);

         if (wasUsed) {

@@ -1044,11 +1106,40 @@ iBool postContextClick_Window(iWindow *d, const SDL_MouseButtonEvent *ev) {

 return iFalse;

}



+void draw_Window(iWindow *d) {

+ if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) {

+ return;

+ }

+ iPaint p;

+ init_Paint(&p);

+ iRoot *root = d->roots[0];

+ setCurrent_Root(root);

+ unsetClip_Paint(&p); /* update clip to full window */

+ const iColor back = get_Color(uiBackground_ColorId);

+ SDL_SetRenderDrawColor(d->render, back.r, back.g, back.b, 255);

+ SDL_RenderClear(d->render);

+ d->frameTime = SDL_GetTicks();

+ if (isExposed_Window(d)) {

+ d->isInvalidated = iFalse;

+ extern int drawCount_;

+ drawRoot_Widget(root->widget);

+#if !defined (NDEBUG)

+ draw_Text(defaultBold_FontId, safeRect_Root(root).pos, red_ColorId, "%d", drawCount_);

+ drawCount_ = 0;

+#endif 

+ }

+// drawRectThickness_Paint(&p, (iRect){ zero_I2(), sub_I2(d->size, one_I2()) }, gap_UI / 4, uiSeparator_ColorId);

+ setCurrent_Root(NULL);

+ SDL_RenderPresent(d->render);

+}

+

void draw_MainWindow(iMainWindow *d) {

+ /* TODO: Try to make this a specialization of draw_Window? */

 iWindow *w = as_Window(d);

- if (w->isDrawFrozen) {

+ if (d->isDrawFrozen) {

     return;

 }

+ setCurrent_Text(d->base.text);

 /* Check if root needs resizing. */ {

     iInt2 renderSize;

     SDL_GetRendererOutputSize(w->render, &renderSize.x, &renderSize.y);

@@ -1180,7 +1271,7 @@ void setUiScale_Window(iWindow *d, float uiScale) {

 }

}



-void setFreezeDraw_Window(iWindow *d, iBool freezeDraw) {

+void setFreezeDraw_MainWindow(iMainWindow *d, iBool freezeDraw) {

 d->isDrawFrozen = freezeDraw;

}



@@ -1231,8 +1322,23 @@ iWindow *get_Window(void) {

 return theWindow_;

}



+void setCurrent_Window(iAnyWindow *d) {

+ theWindow_ = d;

+ if (type_Window(d) == main_WindowType) {

+ theMainWindow_ = d;

+ }

+ if (d) {

+ setCurrent_Text(theWindow_->text);

+ setCurrent_Root(theWindow_->keyRoot);

+ }

+ else {

+ setCurrent_Text(NULL);

+ setCurrent_Root(NULL);

+ }

+}

+

iMainWindow *get_MainWindow(void) {

- return as_MainWindow(theWindow_);

+ return theMainWindow_;

}



iBool isOpenGLRenderer_Window(void) {

@@ -1272,7 +1378,7 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) {

 iAssert(current_Root() == NULL);

 if (d->splitMode != splitMode) {

     int oldCount = numRoots_Window(w);

- setFreezeDraw_Window(w, iTrue);

+ setFreezeDraw_MainWindow(d, iTrue);

     if (oldCount == 2 && splitMode == 0) {

         /* Keep references to the tabs of the second root. */

         const iDocumentWidget *curPage = document_Root(w->keyRoot);

@@ -1311,6 +1417,7 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) {

         }

         w->roots[newRootIndex] = new_Root();

         w->keyRoot             = w->roots[newRootIndex];

+ w->keyRoot->window = w;

         setCurrent_Root(w->roots[newRootIndex]);

         createUserInterface_Root(w->roots[newRootIndex]);

         if (!isEmpty_String(d->pendingSplitUrl)) {

@@ -1471,3 +1578,25 @@ int snap_MainWindow(const iMainWindow *d) {

 }

 return d->place.snap;

}

+

+/----------------------------------------------------------------------------------------------/

+

+iWindow *newPopup_Window(iInt2 screenPos, iWidget *rootWidget) {

+ arrange_Widget(rootWidget);

+ iWindow *win =

+ new_Window(popup_WindowType,

+ (iRect){ screenPos, divf_I2(rootWidget->rect.size, get_Window()->pixelRatio) },

+ SDL_WINDOW_ALWAYS_ON_TOP |

+ SDL_WINDOW_POPUP_MENU |

+ SDL_WINDOW_SKIP_TASKBAR);

+#if defined (iPlatformAppleDesktop)

+ hideTitleBar_MacOS(win); /* make it a borderless window */

+#endif

+ iRoot *root = new_Root();

+ win->roots[0] = root;

+ win->keyRoot = root;

+ root->widget = rootWidget;

+ root->window = win;

+ setRoot_Widget(rootWidget, root);

+ return win;

+}

diff --git a/src/ui/window.h b/src/ui/window.h

index 73e92391..f1827931 100644

--- a/src/ui/window.h

+++ b/src/ui/window.h

@@ -29,8 +29,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

#include <SDL_render.h>

#include <SDL_video.h>



+enum iWindowType {

+ main_WindowType,

+ popup_WindowType,

+};

+

iDeclareType(MainWindow)

+iDeclareType(Text)

iDeclareType(Window)

+ 

+iDeclareTypeConstructionArgs(Window, enum iWindowType type, iRect rect, uint32_t flags)

iDeclareTypeConstructionArgs(MainWindow, iRect rect)

 

typedef iAny iAnyWindow;

@@ -71,15 +79,9 @@ enum iWindowSplit {

 noEvents_WindowSplit = iBit(11),

};



-enum iWindowType {

- main_WindowType,

- popup_WindowType,

-};

-

struct Impl_Window {

 enum iWindowType type;

 SDL_Window *  win;

- iBool isDrawFrozen; /* avoids premature draws while restoring window state */

 iBool         isExposed;

 iBool         isMinimized;

 iBool         isMouseInside;

@@ -102,11 +104,13 @@ struct Impl_Window {

 iRoot *       roots[2];     /* root widget and UI state; second one is for split mode */

 iRoot *       keyRoot;      /* root that has the current keyboard input focus */

 SDL_Texture * borderShadow;

+ iText * text;

};



struct Impl_MainWindow {

 iWindow       base;

 iWindowPlacement place;

+ iBool isDrawFrozen; /* avoids premature draws while restoring window state */

 int           splitMode;

 int           pendingSplitMode;

 iString *     pendingSplitUrl; /* URL to open in a newly opened split */

@@ -115,7 +119,10 @@ struct Impl_MainWindow {

};



iLocalDef enum iWindowType type_Window(const iAnyWindow *d) {

- return ((const iWindow *) d)->type;

+ if (d) {

+ return ((const iWindow *) d)->type;

+ }

+ return main_WindowType;

}



uint32_t id_Window (const iWindow *);

@@ -131,11 +138,11 @@ int numRoots_Window (const iWindow *);

iRoot * findRoot_Window (const iWindow *, const iWidget *widget);

iRoot * otherRoot_Window (const iWindow *, iRoot *root);



+iBool processEvent_Window (iWindow *, const SDL_Event *);

iBool dispatchEvent_Window (iWindow *, const SDL_Event *);

void invalidate_Window (iAnyWindow ); / discard all cached graphics */

void draw_Window (iWindow *);

void setUiScale_Window (iWindow *, float uiScale);

-void setFreezeDraw_Window (iWindow *, iBool freezeDraw);

void setCursor_Window (iWindow *, int cursor);

iBool setKeyRoot_Window (iWindow *, iRoot *root);

iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *);

@@ -143,6 +150,8 @@ iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *);

iWindow * get_Window (void);

iBool isOpenGLRenderer_Window (void);



+void setCurrent_Window (iAnyWindow *);

+

iLocalDef iBool isExposed_Window(const iWindow *d) {

 iAssert(d);

 return d->isExposed;

@@ -158,6 +167,10 @@ iLocalDef const iWindow *constAs_Window(const iAnyWindow *d) {

 return (const iWindow *) d;

}



+iLocalDef iText *text_Window(const iAnyWindow *d) {

+ return constAs_Window(d)->text;

+}

+

/----------------------------------------------------------------------------------------------/



iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) {

@@ -167,6 +180,7 @@ iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) {



void setTitle_MainWindow (iMainWindow *, const iString *title);

void setSnap_MainWindow (iMainWindow *, int snapMode);

+void setFreezeDraw_MainWindow (iMainWindow *, iBool freezeDraw);

void setKeyboardHeight_MainWindow (iMainWindow *, int height);

void setSplitMode_MainWindow (iMainWindow *, int splitMode);

void checkPendingSplit_MainWindow (iMainWindow *);

@@ -196,3 +210,7 @@ iLocalDef const iMainWindow *constAs_MainWindow(const iAnyWindow *d) {

 iAssert(type_Window(d) == main_WindowType);

 return (const iMainWindow *) d;

}

+

+/----------------------------------------------------------------------------------------------/

+

+iWindow * newPopup_Window (iInt2 screenPos, iWidget *rootWidget);

Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.7/pcdiff/2d81addf78d6a8b0fb2f2959b04a385c4adffdf2
Status Code
Success (20)
Meta
text/plain
Capsule Response Time
102.982694 milliseconds
Gemini-to-HTML Time
35.187643 milliseconds

This content has been proxied by September (ba2dc).