=> 2d81addf78d6a8b0fb2f2959b04a385c4adffdf2
[1mdiff --git a/src/app.c b/src/app.c[m [1mindex 91b3a06d..c1c1da27 100644[m [1m--- a/src/app.c[m [1m+++ b/src/app.c[m [36m@@ -118,6 +118,7 @@[m [mstruct Impl_App {[m iVisited * visited;[m iBookmarks * bookmarks;[m iMainWindow *window;[m [32m+[m[32m iPtrArray popupWindows;[m iSortedArray tickers; /* per-frame callbacks, used for animations */[m uint32_t lastTickerTime;[m uint32_t elapsedSinceLastTicker;[m [36m@@ -801,6 +802,7 @@[m [mstatic void init_App_(iApp *d, int argc, char **argv) {[m d->initialWindowRect.size.y = toInt_String(value_CommandLineArg(arg, 0));[m }[m }[m [32m+[m[32m init_PtrArray(&d->popupWindows);[m d->window = new_MainWindow(d->initialWindowRect);[m load_Visited(d->visited, dataDir_App_());[m load_Bookmarks(d->bookmarks, dataDir_App_());[m [36m@@ -853,6 +855,11 @@[m [mstatic void init_App_(iApp *d, int argc, char **argv) {[m }[m [m static void deinit_App(iApp *d) {[m [32m+[m[32m iReverseForEach(PtrArray, i, &d->popupWindows) {[m [32m+[m[32m delete_Window(i.ptr);[m [32m+[m[32m }[m [32m+[m[32m iAssert(isEmpty_PtrArray(&d->popupWindows));[m [32m+[m[32m deinit_PtrArray(&d->popupWindows);[m #if defined (LAGRANGE_ENABLE_IDLE_SLEEP)[m SDL_RemoveTimer(d->sleepTimer);[m #endif[m [36m@@ -1086,6 +1093,15 @@[m [mstatic iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *ev[m return SDL_PollEvent(event);[m }[m [m [32m+[m[32mstatic const iPtrArray *listWindows_App_(const iApp *d) {[m [32m+[m[32m iPtrArray *list = collectNew_PtrArray();[m [32m+[m[32m iReverseConstForEach(PtrArray, i, &d->popupWindows) {[m [32m+[m[32m pushBack_PtrArray(list, i.ptr);[m [32m+[m[32m }[m [32m+[m[32m pushBack_PtrArray(list, d->window);[m [32m+[m[32m return list;[m [32m+[m[32m}[m [32m+[m void processEvents_App(enum iAppEventMode eventMode) {[m iApp *d = &app_;[m iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */[m [36m@@ -1125,17 +1141,17 @@[m [mvoid processEvents_App(enum iAppEventMode eventMode) {[m #if defined (iPlatformAppleMobile)[m updateNowPlayingInfo_iOS();[m #endif[m [31m- setFreezeDraw_Window(as_Window(d), iTrue);[m [32m+[m[32m setFreezeDraw_MainWindow(d->window, iTrue);[m savePrefs_App_(d);[m saveState_App_(d);[m break;[m case SDL_APP_TERMINATING:[m [31m- setFreezeDraw_Window(as_Window(d), iTrue);[m [32m+[m[32m setFreezeDraw_MainWindow(d->window, iTrue);[m savePrefs_App_(d);[m saveState_App_(d);[m break;[m case SDL_DROPFILE: {[m [31m- iBool wasUsed = processEvent_MainWindow(d->window, &ev);[m [32m+[m[32m iBool wasUsed = processEvent_Window(as_Window(d->window), &ev);[m if (!wasUsed) {[m iBool newTab = iFalse;[m if (elapsedSeconds_Time(&d->lastDropTime) < 0.1) {[m [36m@@ -1175,23 +1191,6 @@[m [mvoid processEvents_App(enum iAppEventMode eventMode) {[m }[m d->isIdling = iFalse;[m #endif[m [31m- if (ev.type == SDL_USEREVENT && ev.user.code == arrange_UserEventCode) {[m [31m- printf("[App] rearrange\n");[m [31m- resize_MainWindow(d->window, -1, -1);[m [31m- iForIndices(i, d->window->base.roots) {[m [31m- if (d->window->base.roots[i]) {[m [31m- d->window->base.roots[i]->pendingArrange = iFalse;[m [31m- }[m [31m- }[m [31m-// if (ev.user.data2 == d->window->roots[0]) {[m [31m-// arrange_Widget(d->window->roots[0]->widget);[m [31m-// }[m [31m-// else if (d->window->roots[1]) {[m [31m-// arrange_Widget(d->window->roots[1]->widget);[m [31m-// }[m [31m-// postRefresh_App();[m [31m- continue;[m [31m- }[m gotEvents = iTrue;[m /* Keyboard modifier mapping. */[m if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) {[m [36m@@ -1268,10 +1267,22 @@[m [mvoid processEvents_App(enum iAppEventMode eventMode) {[m }[m }[m #endif[m [31m- d->window->base.lastHover = d->window->base.hover;[m [31m- iBool wasUsed = processEvent_MainWindow(d->window, &ev);[m [32m+[m[32m /* Per-window processing. */[m [32m+[m[32m iBool wasUsed = iFalse;[m [32m+[m[32m const iPtrArray *windows = listWindows_App_(d);[m [32m+[m[32m iConstForEach(PtrArray, iter, windows) {[m [32m+[m[32m iWindow *window = iter.ptr;[m [32m+[m[32m setCurrent_Window(window);[m [32m+[m[32m window->lastHover = window->hover;[m [32m+[m[32m wasUsed = processEvent_Window(window, &ev);[m [32m+[m[32m if (ev.type == SDL_MOUSEMOTION) {[m [32m+[m[32m break;[m [32m+[m[32m }[m [32m+[m[32m if (wasUsed) break;[m [32m+[m[32m }[m [32m+[m[32m setCurrent_Window(d->window);[m if (!wasUsed) {[m [31m- /* There may be a key bindings for this. */[m [32m+[m[32m /* There may be a key binding for this. */[m wasUsed = processEvent_Keys(&ev);[m }[m if (!wasUsed) {[m [36m@@ -1289,24 +1300,32 @@[m [mvoid processEvents_App(enum iAppEventMode eventMode) {[m handleCommand_MacOS(command_UserEvent(&ev));[m #endif[m if (isMetricsChange_UserEvent(&ev)) {[m [31m- iForIndices(i, d->window->base.roots) {[m [31m- iRoot *root = d->window->base.roots[i];[m [31m- if (root) {[m [31m- arrange_Widget(root->widget);[m [31m- }[m [32m+[m[32m iConstForEach(PtrArray, iter, windows) {[m [32m+[m[32m iWindow *window = iter.ptr;[m [32m+[m[32m iForIndices(i, window->roots) {[m [32m+[m[32m iRoot *root = window->roots[i];[m [32m+[m[32m if (root) {[m [32m+[m[32m arrange_Widget(root->widget);[m [32m+[m[32m }[m [32m+[m[32m }[m[41m [m }[m }[m if (!wasUsed) {[m /* No widget handled the command, so we'll do it. */[m [32m+[m[32m setCurrent_Window(d->window);[m handleCommand_App(ev.user.data1);[m }[m /* Allocated by postCommand_Apps(). */[m free(ev.user.data1);[m }[m [31m- /* Update when hover has changed. */[m [31m- if (d->window->base.lastHover != d->window->base.hover) {[m [31m- refresh_Widget(d->window->base.lastHover);[m [31m- refresh_Widget(d->window->base.hover);[m [32m+[m[32m /* Refresh after hover changes. */ {[m [32m+[m[32m iConstForEach(PtrArray, iter, windows) {[m [32m+[m[32m iWindow *window = iter.ptr;[m [32m+[m[32m if (window->lastHover != window->hover) {[m [32m+[m[32m refresh_Widget(window->lastHover);[m [32m+[m[32m refresh_Widget(window->hover);[m [32m+[m[32m }[m [32m+[m[32m }[m }[m break;[m }[m [36m@@ -1394,25 +1413,46 @@[m [mstatic int run_App_(iApp *d) {[m [m void refresh_App(void) {[m iApp *d = &app_;[m [31m- iForIndices(i, d->window->base.roots) {[m [31m- iRoot *root = d->window->base.roots[i];[m [31m- if (root) {[m [31m- destroyPending_Root(root);[m [31m- }[m [31m- }[m #if defined (LAGRANGE_ENABLE_IDLE_SLEEP)[m if (d->warmupFrames == 0 && d->isIdling) {[m return;[m }[m #endif[m [32m+[m[32m const iPtrArray *windows = listWindows_App_(d);[m [32m+[m[32m /* Destroy pending widgets. */ {[m [32m+[m[32m iConstForEach(PtrArray, j, windows) {[m[41m [m [32m+[m[32m iWindow *win = j.ptr;[m [32m+[m[32m setCurrent_Window(win);[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 destroyPending_Root(root);[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m /* TODO: Pending refresh is window-specific. */[m if (!exchange_Atomic(&d->pendingRefresh, iFalse)) {[m return;[m }[m [31m-// iTime draw;[m [31m-// initCurrent_Time(&draw);[m [31m- draw_MainWindow(d->window);[m [31m-// printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000));[m [31m-// fflush(stdout);[m [32m+[m[32m /* Draw each window. */ {[m [32m+[m[32m iConstForEach(PtrArray, j, windows) {[m [32m+[m[32m iWindow *win = j.ptr;[m [32m+[m[32m setCurrent_Window(win);[m [32m+[m[32m switch (win->type) {[m [32m+[m[32m case main_WindowType:[m [32m+[m[32m // iTime draw;[m [32m+[m[32m // initCurrent_Time(&draw);[m [32m+[m[32m draw_MainWindow(as_MainWindow(win));[m [32m+[m[32m // printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000));[m [32m+[m[32m // fflush(stdout);[m [32m+[m[32m break;[m [32m+[m[32m default:[m [32m+[m[32m draw_Window(win);[m [32m+[m[32m break;[m[41m [m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m }[m if (d->warmupFrames > 0) {[m d->warmupFrames--;[m }[m [36m@@ -1485,12 +1525,6 @@[m [mvoid postRefresh_App(void) {[m }[m }[m [m [31m-void postImmediateRefresh_App(void) {[m [31m- SDL_Event ev = { .type = SDL_USEREVENT };[m [31m- ev.user.code = immediateRefresh_UserEventCode;[m [31m- SDL_PushEvent(&ev);[m [31m-}[m [31m-[m void postCommand_Root(iRoot *d, const char *command) {[m iAssert(command);[m if (strlen(command) == 0) {[m [36m@@ -1546,7 +1580,7 @@[m [mvoid postCommandf_App(const char *command, ...) {[m }[m [m void rootOrder_App(iRoot *roots[2]) {[m [31m- const iWindow *win = as_Window(app_.window);[m [32m+[m[32m const iWindow *win = get_Window();[m roots[0] = win->keyRoot;[m roots[1] = (roots[0] == win->roots[0] ? win->roots[1] : win->roots[0]);[m }[m [36m@@ -1583,6 +1617,16 @@[m [mvoid removeTicker_App(iTickerFunc ticker, iAny *context) {[m remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker });[m }[m [m [32m+[m[32mvoid addPopup_App(iWindow *popup) {[m [32m+[m[32m iApp *d = &app_;[m [32m+[m[32m pushBack_PtrArray(&d->popupWindows, popup);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mvoid removePopup_App(iWindow *popup) {[m [32m+[m[32m iApp *d = &app_;[m [32m+[m[32m removeOne_PtrArray(&d->popupWindows, popup);[m [32m+[m[32m}[m [32m+[m iMimeHooks *mimeHooks_App(void) {[m return app_.mimehooks;[m }[m [36m@@ -1836,8 +1880,10 @@[m [miDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe[m static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {[m iApp *d = &app_;[m if (equal_Command(cmd, "ident.showmore")) {[m [31m- iForEach(ObjectList, i,[m [31m- children_Widget(findChild_Widget(dlg, isUsingPanelLayout_Mobile() ? "panel.top" : "headings"))) {[m [32m+[m[32m iForEach(ObjectList,[m [32m+[m[32m i,[m [32m+[m[32m children_Widget(findChild_Widget([m [32m+[m[32m dlg, isUsingPanelLayout_Mobile() ? "panel.top" : "headings"))) {[m if (flags_Widget(i.object) & collapse_WidgetFlag) {[m setFlags_Widget(i.object, hidden_WidgetFlag, iFalse);[m }[m [36m@@ -1978,9 +2024,15 @@[m [mconst iString *searchQueryUrl_App(const iString *queryStringUnescaped) {[m return collectNewFormat_String("%s?%s", cstr_String(&d->prefs.searchUrl), cstr_String(escaped));[m }[m [m [32m+[m[32mstatic void resetFonts_App_(iApp *d) {[m [32m+[m[32m iConstForEach(PtrArray, win, listWindows_App_(d)) {[m [32m+[m[32m resetFonts_Text(text_Window(win.ptr));[m [32m+[m[32m }[m[41m [m [32m+[m[32m}[m [32m+[m iBool handleCommand_App(const char *cmd) {[m iApp *d = &app_;[m [31m- const iBool isFrozen = !d->window || d->window->base.isDrawFrozen;[m [32m+[m[32m const iBool isFrozen = !d->window || d->window->isDrawFrozen;[m if (equal_Command(cmd, "config.error")) {[m makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR",[m format_CStr("Error in config file: %s\n"[m [36m@@ -2047,18 +2099,18 @@[m [miBool handleCommand_App(const char *cmd) {[m return iTrue;[m }[m else if (equal_Command(cmd, "font.reset")) {[m [31m- resetFonts_Text();[m [32m+[m[32m resetFonts_App_(d);[m return iTrue;[m }[m else if (equal_Command(cmd, "font.user")) {[m const char *path = suffixPtr_Command(cmd, "path");[m if (cmp_String(&d->prefs.symbolFontPath, path)) {[m if (!isFrozen) {[m [31m- setFreezeDraw_Window(get_Window(), iTrue);[m [32m+[m[32m setFreezeDraw_MainWindow(get_MainWindow(), iTrue);[m }[m setCStr_String(&d->prefs.symbolFontPath, path);[m loadUserFonts_Text();[m [31m- resetFonts_Text();[m [32m+[m[32m resetFonts_App_(d);[m if (!isFrozen) {[m postCommand_App("font.changed");[m postCommand_App("window.unfreeze");[m [36m@@ -2068,10 +2120,10 @@[m [miBool handleCommand_App(const char *cmd) {[m }[m else if (equal_Command(cmd, "font.set")) {[m if (!isFrozen) {[m [31m- setFreezeDraw_Window(get_Window(), iTrue);[m [32m+[m[32m setFreezeDraw_MainWindow(get_MainWindow(), iTrue);[m }[m d->prefs.font = arg_Command(cmd);[m [31m- setContentFont_Text(d->prefs.font);[m [32m+[m[32m setContentFont_Text(text_Window(d->window), d->prefs.font);[m if (!isFrozen) {[m postCommand_App("font.changed");[m postCommand_App("window.unfreeze");[m [36m@@ -2080,10 +2132,10 @@[m [miBool handleCommand_App(const char *cmd) {[m }[m else if (equal_Command(cmd, "headingfont.set")) {[m if (!isFrozen) {[m [31m- setFreezeDraw_Window(get_Window(), iTrue);[m [32m+[m[32m setFreezeDraw_MainWindow(get_MainWindow(), iTrue);[m }[m d->prefs.headingFont = arg_Command(cmd);[m [31m- setHeadingFont_Text(d->prefs.headingFont);[m [32m+[m[32m setHeadingFont_Text(text_Window(d->window), d->prefs.headingFont);[m if (!isFrozen) {[m postCommand_App("font.changed");[m postCommand_App("window.unfreeze");[m [36m@@ -2092,10 +2144,10 @@[m [miBool handleCommand_App(const char *cmd) {[m }[m else if (equal_Command(cmd, "zoom.set")) {[m if (!isFrozen) {[m [31m- setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */[m [32m+[m[32m setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */[m }[m d->prefs.zoomPercent = arg_Command(cmd);[m [31m- setContentFontSize_Text((float) d->prefs.zoomPercent / 100.0f);[m [32m+[m[32m setContentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f);[m if (!isFrozen) {[m postCommand_App("font.changed");[m postCommand_App("window.unfreeze");[m [36m@@ -2104,14 +2156,14 @@[m [miBool handleCommand_App(const char *cmd) {[m }[m else if (equal_Command(cmd, "zoom.delta")) {[m if (!isFrozen) {[m [31m- setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */[m [32m+[m[32m setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */[m }[m int delta = arg_Command(cmd);[m if (d->prefs.zoomPercent < 100 || (delta < 0 && d->prefs.zoomPercent == 100)) {[m delta /= 2;[m }[m d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200);[m [31m- setContentFontSize_Text((float) d->prefs.zoomPercent / 100.0f);[m [32m+[m[32m setContentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f);[m if (!isFrozen) {[m postCommand_App("font.changed");[m postCommand_App("window.unfreeze");[m [36m@@ -2211,7 +2263,7 @@[m [miBool handleCommand_App(const char *cmd) {[m equal_Command(cmd, "prefs.mono.gopher.changed")) {[m const iBool isSet = (arg_Command(cmd) != 0);[m if (!isFrozen) {[m [31m- setFreezeDraw_Window(as_Window(d->window), iTrue);[m [32m+[m[32m setFreezeDraw_MainWindow(get_MainWindow(), iTrue);[m }[m if (startsWith_CStr(cmd, "prefs.mono.gemini")) {[m d->prefs.monospaceGemini = isSet;[m [36m@@ -2936,3 +2988,7 @@[m [miStringSet *listOpenURLs_App(void) {[m iRelease(docs);[m return set;[m }[m [32m+[m [32m+[m[32miMainWindow *mainWindow_App(void) {[m [32m+[m[32m return app_.window;[m [32m+[m[32m}[m [1mdiff --git a/src/app.h b/src/app.h[m [1mindex 08589000..8966e8c7 100644[m [1m--- a/src/app.h[m [1m+++ b/src/app.h[m [36m@@ -22,8 +22,6 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m [m #pragma once[m [m [31m-/* Application core: event loop, base event processing, audio synth. */[m [31m-[m #include[m #include [m #include [m [36m@@ -35,6 +33,7 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m iDeclareType(Bookmarks)[m iDeclareType(DocumentWidget)[m iDeclareType(GmCerts)[m [32m+[m[32miDeclareType(MainWindow)[m iDeclareType(MimeHooks)[m iDeclareType(Periodic)[m iDeclareType(Root)[m [36m@@ -61,14 +60,12 @@[m [menum iAppEventMode {[m enum iUserEventCode {[m command_UserEventCode = 1,[m refresh_UserEventCode,[m [31m- arrange_UserEventCode,[m asleep_UserEventCode,[m /* The start of a potential touch tap event is notified via a custom event because[m sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will[m take, it could turn into a tap-and-hold for example. */[m widgetTapBegins_UserEventCode,[m widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */[m [31m- immediateRefresh_UserEventCode, /* refresh even though more events are pending */[m };[m [m const iString *execPath_App (void);[m [36m@@ -119,8 +116,9 @@[m [miAny * findWidget_App (const char *id);[m void addTicker_App (iTickerFunc ticker, iAny *context);[m void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context);[m void removeTicker_App (iTickerFunc ticker, iAny *context);[m [32m+[m[32mvoid addPopup_App (iWindow *popup);[m [32m+[m[32mvoid removePopup_App (iWindow *popup);[m void postRefresh_App (void);[m [31m-void postImmediateRefresh_App(void);[m void postCommand_Root (iRoot *, const char *command);[m void postCommandf_Root (iRoot *, const char *command, ...);[m void postCommandf_App (const char *command, ...);[m [36m@@ -138,3 +136,5 @@[m [miDocumentWidget * document_Command (const char *cmd);[m [m void openInDefaultBrowser_App (const iString *url);[m void revealPath_App (const iString *path);[m [32m+[m [32m+[m[32miMainWindow *mainWindow_App(void);[m [1mdiff --git a/src/ios.m b/src/ios.m[m [1mindex 3fb0af48..b46fb8dc 100644[m [1m--- a/src/ios.m[m [1m+++ b/src/ios.m[m [36m@@ -247,14 +247,14 @@[m [mdidPickDocumentsAtURLs:(NSArray *)urls {[m UIView *view = [viewController_(get_Window()) view];[m CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil];[m // NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame));[m [31m- iWindow *window = get_Window();[m [31m- const iInt2 rootSize = size_Root(window->roots[0]);[m [31m- const int keyTop = keyboardFrame.origin.y * window->pixelRatio;[m [31m- setKeyboardHeight_Window(window, rootSize.y - keyTop);[m [32m+[m[32m iMainWindow *window = get_MainWindow();[m [32m+[m[32m const iInt2 rootSize = size_Root(window->base.roots[0]);[m [32m+[m[32m const int keyTop = keyboardFrame.origin.y * window->base.pixelRatio;[m [32m+[m[32m setKeyboardHeight_MainWindow(window, rootSize.y - keyTop);[m }[m [m -(void)keyboardOffScreen:(NSNotification *)notification {[m [31m- setKeyboardHeight_Window(get_Window(), 0);[m [32m+[m[32m setKeyboardHeight_MainWindow(get_MainWindow(), 0);[m }[m @end[m [m [1mdiff --git a/src/macos.h b/src/macos.h[m [1mindex 0d3f097a..20b95943 100644[m [1m--- a/src/macos.h[m [1m+++ b/src/macos.h[m [36m@@ -24,6 +24,8 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m [m #include "ui/util.h"[m [m [32m+[m[32miDeclareType(Window)[m [32m+[m /* Platform-specific functionality for macOS */[m [m iBool shouldDefaultToMetalRenderer_MacOS (void);[m [36m@@ -31,6 +33,7 @@[m [miBool shouldDefaultToMetalRenderer_MacOS (void);[m void enableMomentumScroll_MacOS (void);[m void registerURLHandler_MacOS (void);[m void setupApplication_MacOS (void);[m [32m+[m[32mvoid hideTitleBar_MacOS (iWindow *window);[m void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count);[m void removeMenu_MacOS (int atIndex);[m void enableMenu_MacOS (const char *menuLabel, iBool enable);[m [1mdiff --git a/src/macos.m b/src/macos.m[m [1mindex d588fa4a..298db0f8 100644[m [1m--- a/src/macos.m[m [1m+++ b/src/macos.m[m [36m@@ -30,6 +30,7 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m #include "ui/window.h"[m [m #include [m [32m+[m[32m#include [m [m #import [m [m [36m@@ -51,6 +52,16 @@[m [mstatic iInt2 macVer_(void) {[m return init_I2(10, 10);[m }[m [m [32m+[m[32mstatic NSWindow *nsWindow_(SDL_Window *window) {[m [32m+[m[32m SDL_SysWMinfo wm;[m [32m+[m[32m SDL_VERSION(&wm.version);[m [32m+[m[32m if (SDL_GetWindowWMInfo(window, &wm)) {[m [32m+[m[32m return wm.info.cocoa.window;[m [32m+[m[32m }[m [32m+[m[32m iAssert(false);[m [32m+[m[32m return nil;[m [32m+[m[32m}[m [32m+[m static NSString *currentSystemAppearance_(void) {[m /* This API does not exist on 10.13. */[m if ([NSApp respondsToSelector:@selector(effectiveAppearance)]) {[m [36m@@ -370,6 +381,11 @@[m [mvoid setupApplication_MacOS(void) {[m windowCloseItem.action = @selector(closeTab);[m }[m [m [32m+[m[32mvoid hideTitleBar_MacOS(iWindow *window) {[m [32m+[m[32m NSWindow *w = nsWindow_(window->win);[m [32m+[m[32m w.styleMask = 0; /* borderless */[m [32m+[m[32m}[m [32m+[m void enableMenu_MacOS(const char *menuLabel, iBool enable) {[m menuLabel = translateCStr_Lang(menuLabel);[m NSApplication *app = [NSApplication sharedApplication];[m [36m@@ -377,7 +393,6 @@[m [mvoid enableMenu_MacOS(const char *menuLabel, iBool enable) {[m NSString *label = [NSString stringWithUTF8String:menuLabel];[m NSMenuItem *menuItem = [appMenu itemAtIndex:[appMenu indexOfItemWithTitle:label]];[m [menuItem setEnabled:enable];[m [31m- [label release];[m }[m [m void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) {[m [1mdiff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c[m [1mindex ed9e41d6..6f9824de 100644[m [1m--- a/src/ui/documentwidget.c[m [1m+++ b/src/ui/documentwidget.c[m [36m@@ -750,7 +750,7 @@[m [mstatic uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) {[m if (document_App() != d) {[m return 0;[m }[m [31m- if (get_Window()->isDrawFrozen) {[m [32m+[m[32m if (as_MainWindow(window_Widget(d))->isDrawFrozen) {[m return 0;[m }[m static const uint32_t invalidInterval_ = ~0u;[m [1mdiff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c[m [1mindex a561d5bd..f02bf408 100644[m [1m--- a/src/ui/inputwidget.c[m [1m+++ b/src/ui/inputwidget.c[m [36m@@ -2352,7 +2352,7 @@[m [mstatic void draw_InputWidget_(const iInputWidget *d) {[m }[m /* Draw the insertion point. */[m if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) &&[m [31m- isEmpty_Range(&d->mark)) {[m [32m+[m[32m (deviceType_App() == desktop_AppDeviceType || isEmpty_Range(&d->mark))) {[m iInt2 curSize;[m iRangecc cursorChar = iNullRange;[m int visWrapsAbove = 0;[m [1mdiff --git a/src/ui/root.c b/src/ui/root.c[m [1mindex 52a08eca..9e290b05 100644[m [1m--- a/src/ui/root.c[m [1m+++ b/src/ui/root.c[m [36m@@ -298,16 +298,6 @@[m [mvoid destroyPending_Root(iRoot *d) {[m setCurrent_Root(oldRoot);[m }[m [m [31m-void postArrange_Root(iRoot *d) {[m [31m- if (!d->pendingArrange) {[m [31m- d->pendingArrange = iTrue;[m [31m- SDL_Event ev = { .type = SDL_USEREVENT };[m [31m- ev.user.code = arrange_UserEventCode;[m [31m- ev.user.data2 = d;[m [31m- SDL_PushEvent(&ev);[m [31m- }[m [31m-}[m [31m-[m iPtrArray *onTop_Root(iRoot *d) {[m if (!d->onTop) {[m d->onTop = new_PtrArray();[m [1mdiff --git a/src/ui/root.h b/src/ui/root.h[m [1mindex 740e97c9..04dd5e16 100644[m [1m--- a/src/ui/root.h[m [1m+++ b/src/ui/root.h[m [36m@@ -9,6 +9,7 @@[m [miDeclareType(Root)[m [m struct Impl_Root {[m iWidget * widget;[m [32m+[m[32m iWindow * window;[m iPtrArray *onTop; /* order is important; last one is topmost */[m iPtrSet * pendingDestruction;[m iBool pendingArrange;[m [36m@@ -29,7 +30,6 @@[m [miAnyObject *findWidget_Root (const char *id); /* under curre[m [m iPtrArray * onTop_Root (iRoot *);[m void destroyPending_Root (iRoot *);[m [31m-void postArrange_Root (iRoot *);[m [m void updateMetrics_Root (iRoot *);[m void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */[m [1mdiff --git a/src/ui/text.c b/src/ui/text.c[m [1mindex f7fff4bc..bf71b0e9 100644[m [1m--- a/src/ui/text.c[m [1m+++ b/src/ui/text.c[m [36m@@ -290,7 +290,9 @@[m [mstruct Impl_Text {[m iRegExp * ansiEscape;[m };[m [m [31m-static iText text_;[m [32m+[m[32miDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render)[m [32m+[m [32m+[m[32mstatic iText *activeText_;[m static iBlock *userFont_;[m [m static void initFonts_Text_(iText *d) {[m [36m@@ -501,8 +503,7 @@[m [mvoid loadUserFonts_Text(void) {[m }[m }[m [m [31m-void init_Text(SDL_Renderer *render) {[m [31m- iText *d = &text_;[m [32m+[m[32mvoid init_Text(iText *d, SDL_Renderer *render) {[m loadUserFonts_Text();[m d->contentFont = nunito_TextFont;[m d->headingFont = nunito_TextFont;[m [36m@@ -521,8 +522,7 @@[m [mvoid init_Text(SDL_Renderer *render) {[m initFonts_Text_(d);[m }[m [m [31m-void deinit_Text(void) {[m [31m- iText *d = &text_;[m [32m+[m[32mvoid deinit_Text(iText *d) {[m SDL_FreePalette(d->grayscale);[m deinitFonts_Text_(d);[m deinitCache_Text_(d);[m [36m@@ -530,30 +530,34 @@[m [mvoid deinit_Text(void) {[m iRelease(d->ansiEscape);[m }[m [m [32m+[m[32mvoid setCurrent_Text(iText *d) {[m [32m+[m[32m activeText_ = d;[m [32m+[m[32m}[m [32m+[m void setOpacity_Text(float opacity) {[m [31m- SDL_SetTextureAlphaMod(text_.cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f);[m [32m+[m[32m SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f);[m }[m [m [31m-void setContentFont_Text(enum iTextFont font) {[m [31m- if (text_.contentFont != font) {[m [31m- text_.contentFont = font;[m [31m- resetFonts_Text();[m [32m+[m[32mvoid setContentFont_Text(iText *d, enum iTextFont font) {[m [32m+[m[32m if (d->contentFont != font) {[m [32m+[m[32m d->contentFont = font;[m [32m+[m[32m resetFonts_Text(d);[m }[m }[m [m [31m-void setHeadingFont_Text(enum iTextFont font) {[m [31m- if (text_.headingFont != font) {[m [31m- text_.headingFont = font;[m [31m- resetFonts_Text();[m [32m+[m[32mvoid setHeadingFont_Text(iText *d, enum iTextFont font) {[m [32m+[m[32m if (d->headingFont != font) {[m [32m+[m[32m d->headingFont = font;[m [32m+[m[32m resetFonts_Text(d);[m }[m }[m [m [31m-void setContentFontSize_Text(float fontSizeFactor) {[m [32m+[m[32mvoid setContentFontSize_Text(iText *d, float fontSizeFactor) {[m fontSizeFactor *= contentScale_Text_;[m iAssert(fontSizeFactor > 0);[m [31m- if (iAbs(text_.contentFontSize - fontSizeFactor) > 0.001f) {[m [31m- text_.contentFontSize = fontSizeFactor;[m [31m- resetFonts_Text();[m [32m+[m[32m if (iAbs(d->contentFontSize - fontSizeFactor) > 0.001f) {[m [32m+[m[32m d->contentFontSize = fontSizeFactor;[m [32m+[m[32m resetFonts_Text(d);[m }[m }[m [m [36m@@ -565,8 +569,7 @@[m [mstatic void resetCache_Text_(iText *d) {[m initCache_Text_(d);[m }[m [m [31m-void resetFonts_Text(void) {[m [31m- iText *d = &text_;[m [32m+[m[32mvoid resetFonts_Text(iText *d) {[m deinitFonts_Text_(d);[m deinitCache_Text_(d);[m initCache_Text_(d);[m [36m@@ -574,7 +577,7 @@[m [mvoid resetFonts_Text(void) {[m }[m [m iLocalDef iFont *font_Text_(enum iFontId id) {[m [31m- return &text_.fonts[id & mask_FontId];[m [32m+[m[32m return &activeText_->fonts[id & mask_FontId];[m }[m [m static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) {[m [36m@@ -584,7 +587,7 @@[m [mstatic SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, fl[m SDL_Surface *surface8 =[m SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8);[m SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE);[m [31m- SDL_SetSurfacePalette(surface8, text_.grayscale);[m [32m+[m[32m SDL_SetSurfacePalette(surface8, activeText_->grayscale);[m #if LAGRANGE_RASTER_DEPTH != 8[m /* Convert to the cache format. */[m SDL_Surface *surf = SDL_ConvertSurfaceFormat(surface8, LAGRANGE_RASTER_FORMAT, 0);[m [36m@@ -631,7 +634,7 @@[m [mstatic void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) {[m &d->font, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1);[m glRect->size = init_I2(x1 - x0, y1 - y0);[m /* Determine placement in the glyph cache texture, advancing in rows. */[m [31m- glRect->pos = assignCachePos_Text_(&text_, glRect->size);[m [32m+[m[32m glRect->pos = assignCachePos_Text_(activeText_, glRect->size);[m glyph->d[hoff] = init_I2(x0, y0);[m glyph->d[hoff].y += d->vertOffset;[m if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */[m [36m@@ -737,11 +740,11 @@[m [mstatic iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) {[m }[m else {[m /* If the cache is running out of space, clear it and we'll recache what's needed currently. */[m [31m- if (text_.cacheBottom > text_.cacheSize.y - maxGlyphHeight_Text_(&text_)) {[m [32m+[m[32m if (activeText_->cacheBottom > activeText_->cacheSize.y - maxGlyphHeight_Text_(activeText_)) {[m #if !defined (NDEBUG)[m printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout);[m #endif[m [31m- resetCache_Text_(&text_);[m [32m+[m[32m resetCache_Text_(activeText_);[m }[m glyph = new_Glyph(glyphIndex);[m glyph->font = d;[m [36m@@ -858,7 +861,7 @@[m [mstatic void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i[m }[m [m static enum iFontId fontId_Text_(const iFont *font) {[m [31m- return (enum iFontId) (font - text_.fonts);[m [32m+[m[32m return (enum iFontId) (font - activeText_->fonts);[m }[m [m static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) {[m [36m@@ -949,7 +952,7 @@[m [mstatic void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh[m /* Do a regexp match in the source text. */[m iRegExpMatch m;[m init_RegExpMatch(&m);[m [31m- if (match_RegExp(text_.ansiEscape, srcPos, d->source.end - srcPos, &m)) {[m [32m+[m[32m if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) {[m finishRun_AttributedText_(d, &run, pos - 1);[m run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1),[m tmParagraph_ColorId);[m [36m@@ -1082,9 +1085,9 @@[m [mstatic void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {[m while (index < size_Array(glyphIndices)) {[m for (; index < size_Array(glyphIndices); index++) {[m const uint32_t glyphIndex = constValue_Array(glyphIndices, index, uint32_t);[m [31m- const int lastCacheBottom = text_.cacheBottom;[m [32m+[m[32m const int lastCacheBottom = activeText_->cacheBottom;[m iGlyph *glyph = glyphByIndex_Font_(d, glyphIndex);[m [31m- if (text_.cacheBottom < lastCacheBottom) {[m [32m+[m[32m if (activeText_->cacheBottom < lastCacheBottom) {[m /* The cache was reset due to running out of space. We need to restart from[m the beginning! */[m bufX = 0;[m [36m@@ -1103,7 +1106,7 @@[m [mstatic void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {[m LAGRANGE_RASTER_DEPTH,[m LAGRANGE_RASTER_FORMAT);[m SDL_SetSurfaceBlendMode(buf, SDL_BLENDMODE_NONE);[m [31m- SDL_SetSurfacePalette(buf, text_.grayscale);[m [32m+[m[32m SDL_SetSurfacePalette(buf, activeText_->grayscale);[m }[m SDL_Surface *surfaces[2] = {[m !isRasterized_Glyph_(glyph, 0) ?[m [36m@@ -1147,19 +1150,19 @@[m [mstatic void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {[m }[m /* Finished or the buffer is full, copy the glyphs to the cache texture. */[m if (!isEmpty_Array(rasters)) {[m [31m- SDL_Texture *bufTex = SDL_CreateTextureFromSurface(text_.render, buf);[m [32m+[m[32m SDL_Texture *bufTex = SDL_CreateTextureFromSurface(activeText_->render, buf);[m SDL_SetTextureBlendMode(bufTex, SDL_BLENDMODE_NONE);[m if (!isTargetChanged) {[m isTargetChanged = iTrue;[m [31m- oldTarget = SDL_GetRenderTarget(text_.render);[m [31m- SDL_SetRenderTarget(text_.render, text_.cache);[m [32m+[m[32m oldTarget = SDL_GetRenderTarget(activeText_->render);[m [32m+[m[32m SDL_SetRenderTarget(activeText_->render, activeText_->cache);[m }[m // printf("copying %zu rasters from %p\n", size_Array(rasters), bufTex); fflush(stdout);[m iConstForEach(Array, i, rasters) {[m const iRasterGlyph *rg = i.value;[m // iAssert(isEqual_I2(rg->rect.size, rg->glyph->rect[rg->hoff].size));[m const iRect *glRect = &rg->glyph->rect[rg->hoff];[m [31m- SDL_RenderCopy(text_.render,[m [32m+[m[32m SDL_RenderCopy(activeText_->render,[m bufTex,[m (const SDL_Rect *) &rg->rect,[m (const SDL_Rect *) glRect);[m [36m@@ -1179,7 +1182,7 @@[m [mstatic void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {[m SDL_FreeSurface(buf);[m }[m if (isTargetChanged) {[m [31m- SDL_SetRenderTarget(text_.render, oldTarget);[m [32m+[m[32m SDL_SetRenderTarget(activeText_->render, oldTarget);[m }[m }[m [m [36m@@ -1706,9 +1709,9 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m }[m if (~mode & permanentColorFlag_RunMode) {[m const iColor clr = run->fgColor;[m [31m- SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b);[m [32m+[m[32m SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);[m if (args->mode & fillBackground_RunMode) {[m [31m- SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0);[m [32m+[m[32m SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);[m }[m }[m SDL_Rect src;[m [36m@@ -1719,9 +1722,9 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m /* Alpha blending looks much better if the RGB components don't change in[m the partially transparent pixels. */[m /* TODO: Backgrounds of all glyphs should be cleared before drawing anything else. */[m [31m- SDL_RenderFillRect(text_.render, &dst);[m [32m+[m[32m SDL_RenderFillRect(activeText_->render, &dst);[m }[m [31m- SDL_RenderCopy(text_.render, text_.cache, &src, &dst);[m [32m+[m[32m SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst);[m #if 0[m /* Show spaces and direction. */[m if (logicalText[logPos] == 0x20) {[m [36m@@ -1863,7 +1866,7 @@[m [miTextMetrics measureN_Text(int fontId, const char *text, size_t n) {[m }[m [m static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) {[m [31m- iText * d = &text_;[m [32m+[m[32m iText * d = activeText_;[m iFont * font = font_Text_(fontId);[m const iColor clr = get_Color(color & mask_ColorId);[m SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b);[m [36m@@ -2057,7 +2060,7 @@[m [miTextMetrics draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) {[m }[m [m SDL_Texture *glyphCache_Text(void) {[m [31m- return text_.cache;[m [32m+[m[32m return activeText_->cache;[m }[m [m static void freeBitmap_(void *ptr) {[m [36m@@ -2170,7 +2173,7 @@[m [miString *renderBlockChars_Text(const iBlock *fontData, int height, enum iTextBlo[m iDefineTypeConstructionArgs(TextBuf, (iWrapText *wrapText, int font, int color), wrapText, font, color)[m [m void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) {[m [31m- SDL_Renderer *render = text_.render;[m [32m+[m[32m SDL_Renderer *render = activeText_->render;[m d->size = measure_WrapText(wrapText, font).bounds.size;[m SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");[m if (d->size.x * d->size.y) {[m [36m@@ -2191,9 +2194,9 @@[m [mvoid init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) {[m SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE);[m SDL_SetRenderDrawColor(render, 255, 255, 255, 0);[m SDL_RenderClear(render);[m [31m- SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */[m [32m+[m[32m SDL_SetTextureBlendMode(activeText_->cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */[m draw_WrapText(wrapText, font, zero_I2(), color | fillBackground_ColorId);[m [31m- SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND);[m [32m+[m[32m SDL_SetTextureBlendMode(activeText_->cache, SDL_BLENDMODE_BLEND);[m SDL_SetRenderTarget(render, oldTarget);[m origin_Paint = oldOrigin;[m SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND);[m [36m@@ -2212,7 +2215,7 @@[m [mvoid draw_TextBuf(const iTextBuf *d, iInt2 pos, int color) {[m addv_I2(&pos, origin_Paint);[m const iColor clr = get_Color(color);[m SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b);[m [31m- SDL_RenderCopy(text_.render,[m [32m+[m[32m SDL_RenderCopy(activeText_->render,[m d->texture,[m &(SDL_Rect){ 0, 0, d->size.x, d->size.y },[m &(SDL_Rect){ pos.x, pos.y, d->size.x, d->size.y });[m [1mdiff --git a/src/ui/text.h b/src/ui/text.h[m [1mindex ac6cc1c1..1da43818 100644[m [1m--- a/src/ui/text.h[m [1m+++ b/src/ui/text.h[m [36m@@ -139,15 +139,20 @@[m [menum iTextFont {[m [m extern int gap_Text; /* affected by content font size */[m [m [31m-void init_Text (SDL_Renderer *);[m [31m-void deinit_Text (void);[m [32m+[m[32miDeclareType(Text)[m [32m+[m[32miDeclareTypeConstructionArgs(Text, SDL_Renderer *)[m [32m+[m [32m+[m[32mvoid init_Text (iText *, SDL_Renderer *);[m [32m+[m[32mvoid deinit_Text (iText *);[m [32m+[m [32m+[m[32mvoid setCurrent_Text (iText *);[m [m void loadUserFonts_Text (void); /* based on Prefs */[m [m [31m-void setContentFont_Text (enum iTextFont font);[m [31m-void setHeadingFont_Text (enum iTextFont font);[m [31m-void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */[m [31m-void resetFonts_Text (void);[m [32m+[m[32mvoid setContentFont_Text (iText *, enum iTextFont font);[m [32m+[m[32mvoid setHeadingFont_Text (iText *, enum iTextFont font);[m [32m+[m[32mvoid setContentFontSize_Text (iText *, float fontSizeFactor); /* affects all except `default*` fonts */[m [32m+[m[32mvoid resetFonts_Text (iText *);[m [m int lineHeight_Text (int fontId);[m iRect visualBounds_Text (int fontId, iRangecc text);[m [1mdiff --git a/src/ui/text_simple.c b/src/ui/text_simple.c[m [1mindex bf33b4be..8b1de64a 100644[m [1m--- a/src/ui/text_simple.c[m [1m+++ b/src/ui/text_simple.c[m [36m@@ -92,7 +92,7 @@[m [mstatic iRect runSimple_Font_(iFont *d, const iRunArgs *args) {[m }[m if (args->mode & fillBackground_RunMode) {[m const iColor initial = get_Color(args->color);[m [31m- SDL_SetRenderDrawColor(text_.render, initial.r, initial.g, initial.b, 0);[m [32m+[m[32m SDL_SetRenderDrawColor(activeText_->render, initial.r, initial.g, initial.b, 0);[m }[m /* Text rendering is not very straightforward! Let's dive in... */[m iChar prevCh = 0;[m [36m@@ -114,14 +114,14 @@[m [mstatic iRect runSimple_Font_(iFont *d, const iRunArgs *args) {[m chPos++;[m iRegExpMatch m;[m init_RegExpMatch(&m);[m [31m- if (match_RegExp(text_.ansiEscape, chPos, args->text.end - chPos, &m)) {[m [32m+[m[32m if (match_RegExp(activeText_->ansiEscape, chPos, args->text.end - chPos, &m)) {[m if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {[m /* Change the color. */[m const iColor clr =[m ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), tmParagraph_ColorId);[m [31m- SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b);[m [32m+[m[32m SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);[m if (args->mode & fillBackground_RunMode) {[m [31m- SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0);[m [32m+[m[32m SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);[m }[m }[m chPos = end_RegExpMatch(&m);[m [36m@@ -205,9 +205,9 @@[m [mstatic iRect runSimple_Font_(iFont *d, const iRunArgs *args) {[m }[m if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {[m const iColor clr = get_Color(colorNum);[m [31m- SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b);[m [32m+[m[32m SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);[m if (args->mode & fillBackground_RunMode) {[m [31m- SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0);[m [32m+[m[32m SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);[m }[m }[m prevCh = 0;[m [36m@@ -311,9 +311,9 @@[m [mstatic iRect runSimple_Font_(iFont *d, const iRunArgs *args) {[m if (args->mode & fillBackground_RunMode) {[m /* Alpha blending looks much better if the RGB components don't change in[m the partially transparent pixels. */[m [31m- SDL_RenderFillRect(text_.render, &dst);[m [32m+[m[32m SDL_RenderFillRect(activeText_->render, &dst);[m }[m [31m- SDL_RenderCopy(text_.render, text_.cache, &src, &dst);[m [32m+[m[32m SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst);[m }[m xpos += advance;[m if (!isSpace_Char(ch)) {[m [1mdiff --git a/src/ui/util.c b/src/ui/util.c[m [1mindex 721aed2d..38977b96 100644[m [1m--- a/src/ui/util.c[m [1m+++ b/src/ui/util.c[m [36m@@ -613,6 +613,8 @@[m [miBool isAction_Widget(const iWidget *d) {[m /*-----------------------------------------------------------------------------------------------*/[m [m static iBool isCommandIgnoredByMenus_(const char *cmd) {[m [32m+[m[32m if (equal_Command(cmd, "window.focus.lost") ||[m [32m+[m[32m equal_Command(cmd, "window.focus.gained")) return iTrue;[m /* TODO: Perhaps a common way of indicating which commands are notifications and should not[m be reacted to by menus? */[m return equal_Command(cmd, "media.updated") ||[m [36m@@ -810,6 +812,10 @@[m [mstatic void updateMenuItemFonts_Widget_(iWidget *d) {[m }[m }[m [m [32m+[m[32miLocalDef iBool isUsingMenuPopupWindows_(void) {[m [32m+[m[32m return deviceType_App() == desktop_AppDeviceType;[m [32m+[m[32m}[m [32m+[m void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {[m const iRect rootRect = rect_Root(d->root);[m const iInt2 rootSize = rootRect.size;[m [36m@@ -822,6 +828,26 @@[m [mvoid openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {[m processEvents_App(postedEventsOnly_AppEventMode);[m setFlags_Widget(d, hidden_WidgetFlag, iFalse);[m setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue);[m [32m+[m[32m if (isUsingMenuPopupWindows_()) {[m [32m+[m[32m if (postCommands) {[m [32m+[m[32m postCommand_Widget(d, "menu.opened");[m [32m+[m[32m }[m [32m+[m[32m updateMenuItemFonts_Widget_(d);[m [32m+[m[32m iRoot *oldRoot = current_Root();[m [32m+[m[32m setFlags_Widget(d, keepOnTop_WidgetFlag, iFalse);[m [32m+[m[32m setUserData_Object(d, parent_Widget(d));[m [32m+[m[32m removeChild_Widget(parent_Widget(d), d); /* we'll borrow the widget for a while */[m [32m+[m[32m iInt2 mousePos;[m [32m+[m[32m SDL_GetGlobalMouseState(&mousePos.x, &mousePos.y);[m [32m+[m[32m iWindow *win = newPopup_Window(sub_I2(mousePos, divi_I2(gap2_UI, 2)), d);[m [32m+[m[32m SDL_SetWindowTitle(win->win, "Menu");[m [32m+[m[32m addPopup_App(win); /* window takes the widget */[m [32m+[m[32m SDL_ShowWindow(win->win);[m [32m+[m[32m draw_Window(win);[m [32m+[m[32m setCurrent_Window(mainWindow_App());[m [32m+[m[32m setCurrent_Root(oldRoot);[m [32m+[m[32m return;[m [32m+[m[32m }[m raise_Widget(d);[m setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse);[m if (isPortraitPhone) {[m [36m@@ -836,7 +862,7 @@[m [mvoid openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {[m arrange_Widget(d);[m if (isPortraitPhone) {[m if (isSlidePanel) {[m [31m- d->rect.pos = zero_I2(); //neg_I2(bounds_Widget(parent_Widget(d)).pos);[m [32m+[m[32m d->rect.pos = zero_I2();[m }[m else {[m d->rect.pos = init_I2(0, rootSize.y);[m [36m@@ -856,7 +882,7 @@[m [mvoid openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {[m float l, t, r, b;[m safeAreaInsets_iOS(&l, &t, &r, &b);[m topExcess += t;[m [31m- bottomExcess += iMax(b, get_Window()->keyboardHeight);[m [32m+[m[32m bottomExcess += iMax(b, get_MainWindow()->keyboardHeight);[m leftExcess += l;[m rightExcess += r;[m }[m [36m@@ -884,6 +910,18 @@[m [mvoid closeMenu_Widget(iWidget *d) {[m if (d == NULL || flags_Widget(d) & hidden_WidgetFlag) {[m return; /* Already closed. */[m }[m [32m+[m[32m if (isUsingMenuPopupWindows_()) {[m [32m+[m[32m iWindow *win = window_Widget(d);[m [32m+[m[32m iAssert(type_Window(win) == popup_WindowType);[m [32m+[m[32m iWidget *originalParent = userData_Object(d);[m [32m+[m[32m setUserData_Object(d, NULL);[m [32m+[m[32m win->roots[0]->widget = NULL;[m [32m+[m[32m setRoot_Widget(d, originalParent->root);[m [32m+[m[32m addChild_Widget(originalParent, d);[m [32m+[m[32m setFlags_Widget(d, keepOnTop_WidgetFlag, iTrue);[m [32m+[m[32m SDL_HideWindow(win->win);[m [32m+[m[32m collect_Garbage(win, (iDeleteFunc) delete_Window); /* get rid of it after event processing */[m [32m+[m[32m }[m setFlags_Widget(d, hidden_WidgetFlag, iTrue);[m setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue);[m postRefresh_App();[m [1mdiff --git a/src/ui/widget.c b/src/ui/widget.c[m [1mindex 23c19315..7b33a752 100644[m [1m--- a/src/ui/widget.c[m [1m+++ b/src/ui/widget.c[m [36m@@ -271,6 +271,10 @@[m [miWidget *root_Widget(const iWidget *d) {[m return d ? d->root->widget : NULL;[m }[m [m [32m+[m[32miWindow *window_Widget(const iAnyObject *d) {[m [32m+[m[32m return constAs_Widget(d)->root->window;[m [32m+[m[32m}[m [32m+[m void showCollapsed_Widget(iWidget *d, iBool show) {[m const iBool isVisible = !(d->flags & hidden_WidgetFlag);[m if ((isVisible && !show) || (!isVisible && show)) {[m [36m@@ -979,11 +983,10 @@[m [mvoid unhover_Widget(void) {[m }[m [m iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {[m [31m- //iAssert(d->root == get_Root());[m if (!d->parent) {[m [31m- if (get_Window()->focus && get_Window()->focus->root == d->root && isKeyboardEvent_(ev)) {[m [32m+[m[32m if (window_Widget(d)->focus && window_Widget(d)->focus->root == d->root && isKeyboardEvent_(ev)) {[m /* Root dispatches keyboard events directly to the focused widget. */[m [31m- if (dispatchEvent_Widget(get_Window()->focus, ev)) {[m [32m+[m[32m if (dispatchEvent_Widget(window_Widget(d)->focus, ev)) {[m return iTrue;[m }[m }[m [36m@@ -1012,7 +1015,8 @@[m [miBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {[m }[m }[m else if (ev->type == SDL_MOUSEMOTION &&[m [31m- (!get_Window()->hover || hasParent_Widget(d, get_Window()->hover)) &&[m [32m+[m[32m ev->motion.windowID == SDL_GetWindowID(window_Widget(d)->win) &&[m [32m+[m[32m (!window_Widget(d)->hover || hasParent_Widget(d, window_Widget(d)->hover)) &&[m flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag &&[m ~flags_Widget(d) & disabled_WidgetFlag) {[m if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) {[m [36m@@ -1031,11 +1035,11 @@[m [miBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {[m iReverseForEach(ObjectList, i, d->children) {[m iWidget *child = as_Widget(i.object);[m //iAssert(child->root == d->root);[m [31m- if (child == get_Window()->focus && isKeyboardEvent_(ev)) {[m [32m+[m[32m if (child == window_Widget(d)->focus && isKeyboardEvent_(ev)) {[m continue; /* Already dispatched. */[m }[m if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) {[m [31m- /* Already dispatched. */[m [32m+[m[32m /* Already dispatched. */[m continue;[m }[m if (dispatchEvent_Widget(child, ev)) {[m [36m@@ -1050,7 +1054,7 @@[m [miBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {[m #endif[m #if 0[m if (ev->type == SDL_MOUSEMOTION) {[m [31m- printf("[%p] %s:'%s' (on top) ate the motion\n",[m [32m+[m[32m printf("[%p] %s:'%s' ate the motion\n",[m child, class_Widget(child)->name,[m cstr_String(id_Widget(child)));[m fflush(stdout);[m [36m@@ -1246,7 +1250,7 @@[m [miBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {[m ev->button.x,[m ev->button.y);[m }[m [31m- setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW);[m [32m+[m[32m setCursor_Window(window_Widget(d), SDL_SYSTEM_CURSOR_ARROW);[m return iTrue;[m }[m return iFalse;[m [36m@@ -1270,6 +1274,7 @@[m [miLocalDef iBool isDrawn_Widget_(const iWidget *d) {[m void drawLayerEffects_Widget(const iWidget *d) {[m /* Layered effects are not buffered, so they are drawn here separately. */[m iAssert(isDrawn_Widget_(d));[m [32m+[m[32m iAssert(window_Widget(d) == get_Window());[m iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0;[m iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag;[m if (deviceType_App() == phone_AppDeviceType) {[m [36m@@ -1539,6 +1544,7 @@[m [mstatic void endBufferDraw_Widget_(const iWidget *d) {[m }[m [m void draw_Widget(const iWidget *d) {[m [32m+[m[32m iAssert(window_Widget(d) == get_Window());[m if (!isDrawn_Widget_(d)) {[m if (d->drawBuf) {[m // printf("[%p] drawBuffer released\n", d);[m [36m@@ -1820,7 +1826,17 @@[m [miBool equalWidget_Command(const char *cmd, const iWidget *widget, const char *ch[m if (equal_Command(cmd, checkCommand)) {[m const iWidget *src = pointer_Command(cmd);[m iAssert(!src || strstr(cmd, " ptr:"));[m [31m- return src == widget || hasParent_Widget(src, widget);[m [32m+[m[32m if (src == widget || hasParent_Widget(src, widget)) {[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m [32m+[m[32m// if (src && type_Window(window_Widget(src)) == popup_WindowType) {[m [32m+[m[32m// /* Special case: command was emitted from a popup widget. The popup root widget actually[m [32m+[m[32m// belongs to someone else. */[m [32m+[m[32m// iWidget *realParent = userData_Object(src->root->widget);[m [32m+[m[32m// iAssert(realParent);[m [32m+[m[32m// iAssert(isInstance_Object(realParent, &Class_Widget));[m [32m+[m[32m// return realParent == widget || hasParent_Widget(realParent, widget);[m [32m+[m[32m// }[m }[m return iFalse;[m }[m [36m@@ -1962,6 +1978,10 @@[m [mvoid postCommand_Widget(const iAnyObject *d, const char *cmd, ...) {[m }[m if (!isGlobal) {[m iAssert(isInstance_Object(d, &Class_Widget));[m [32m+[m[32m if (type_Window(window_Widget(d)) == popup_WindowType) {[m [32m+[m[32m postCommandf_Root(((const iWidget *) d)->root, "cancel popup:1 ptr:%p", d);[m [32m+[m[32m d = userData_Object(root_Widget(d));[m [32m+[m[32m }[m appendFormat_String(&str, " ptr:%p", d);[m }[m postCommandString_Root(((const iWidget *) d)->root, &str);[m [1mdiff --git a/src/ui/widget.h b/src/ui/widget.h[m [1mindex 7491cb79..0eab69c1 100644[m [1m--- a/src/ui/widget.h[m [1m+++ b/src/ui/widget.h[m [36m@@ -34,7 +34,8 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m #include [m #include [m [m [31m-iDeclareType(Root) /* each widget is associated with a Root */[m [32m+[m[32miDeclareType(Root) /* each widget is associated with a Root */[m [32m+[m[32miDeclareType(Window) /* each Root is inside a Window */[m [m #define iDeclareWidgetClass(className) \[m iDeclareType(className); \[m [36m@@ -185,6 +186,7 @@[m [mvoid releaseChildren_Widget (iWidget *);[m - inner: 0,0 is at the top left corner of the widget */[m [m iWidget * root_Widget (const iWidget *);[m [32m+[m[32miWindow * window_Widget (const iAnyObject *);[m const iString * id_Widget (const iWidget *);[m int64_t flags_Widget (const iWidget *);[m iRect bounds_Widget (const iWidget *); /* outer bounds */[m [1mdiff --git a/src/ui/window.c b/src/ui/window.c[m [1mindex 92125d81..e9a34ace 100644[m [1m--- a/src/ui/window.c[m [1m+++ b/src/ui/window.c[m [36m@@ -57,7 +57,8 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m #include "stb_image.h"[m #include "stb_image_resize.h"[m [m [31m-static iWindow *theWindow_ = NULL;[m [32m+[m[32mstatic iWindow * theWindow_;[m [32m+[m[32mstatic iMainWindow *theMainWindow_;[m [m #if defined (iPlatformApple) || defined (iPlatformLinux) || defined (iPlatformOther)[m static float initialUiScale_ = 1.0f;[m [36m@@ -67,6 +68,9 @@[m [mstatic float initialUiScale_ = 1.1f;[m [m static iBool isOpenGLRenderer_;[m [m [32m+[m[32miDefineTypeConstructionArgs(Window,[m [32m+[m[32m (enum iWindowType type, iRect rect, uint32_t flags),[m [32m+[m[32m type, rect, flags)[m iDefineTypeConstructionArgs(MainWindow, (iRect rect), rect)[m [m /* TODO: Define menus per platform. */[m [36m@@ -205,6 +209,7 @@[m [mstatic void setupUserInterface_MainWindow(iMainWindow *d) {[m #endif[m /* One root is created by default. */[m d->base.roots[0] = new_Root();[m [32m+[m[32m d->base.roots[0]->window = as_Window(d);[m setCurrent_Root(d->base.roots[0]);[m createUserInterface_Root(d->base.roots[0]);[m setCurrent_Root(NULL);[m [36m@@ -409,7 +414,6 @@[m [mvoid init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags)[m d->mouseGrab = NULL;[m d->focus = NULL;[m d->pendingCursor = NULL;[m [31m- d->isDrawFrozen = iTrue;[m d->isExposed = iFalse;[m d->isMinimized = iFalse;[m d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */[m [36m@@ -441,9 +445,27 @@[m [mvoid init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags)[m d->uiScale = initialUiScale_;[m /* TODO: Ratios, scales, and metrics must be window-specific, not global. */[m setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale);[m [32m+[m[32m d->text = new_Text(d->render);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic void deinitRoots_Window_(iWindow *d) {[m [32m+[m[32m iRecycle();[m [32m+[m[32m iForIndices(i, d->roots) {[m [32m+[m[32m if (d->roots[i]) {[m [32m+[m[32m setCurrent_Root(d->roots[i]);[m [32m+[m[32m delete_Root(d->roots[i]);[m [32m+[m[32m d->roots[i] = NULL;[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m setCurrent_Root(NULL);[m }[m [m void deinit_Window(iWindow *d) {[m [32m+[m[32m if (d->type == popup_WindowType) {[m [32m+[m[32m removePopup_App(d);[m [32m+[m[32m }[m [32m+[m[32m deinitRoots_Window_(d);[m [32m+[m[32m delete_Text(d->text);[m SDL_DestroyRenderer(d->render);[m SDL_DestroyWindow(d->win);[m iForIndices(i, d->cursors) {[m [36m@@ -455,6 +477,7 @@[m [mvoid deinit_Window(iWindow *d) {[m [m void init_MainWindow(iMainWindow *d, iRect rect) {[m theWindow_ = &d->base;[m [32m+[m[32m theMainWindow_ = d;[m uint32_t flags = 0;[m #if defined (iPlatformAppleDesktop)[m SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl");[m [36m@@ -465,13 +488,15 @@[m [mvoid init_MainWindow(iMainWindow *d, iRect rect) {[m #endif[m SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");[m init_Window(&d->base, main_WindowType, rect, flags);[m [31m- d->splitMode = d->pendingSplitMode = 0;[m [31m- d->pendingSplitUrl = new_String();[m [31m- d->place.initialPos = rect.pos;[m [31m- d->place.normalRect = rect;[m [32m+[m[32m d->isDrawFrozen = iTrue;[m [32m+[m[32m d->splitMode = 0;[m [32m+[m[32m d->pendingSplitMode = 0;[m [32m+[m[32m d->pendingSplitUrl = new_String();[m [32m+[m[32m d->place.initialPos = rect.pos;[m [32m+[m[32m d->place.normalRect = rect;[m d->place.lastNotifiedSize = zero_I2();[m [31m- d->place.snap = 0;[m [31m- d->keyboardHeight = 0;[m [32m+[m[32m d->place.snap = 0;[m [32m+[m[32m d->keyboardHeight = 0;[m #if defined(iPlatformMobile)[m const iInt2 minSize = zero_I2(); /* windows aren't independently resizable */[m #else[m [36m@@ -510,9 +535,9 @@[m [mvoid init_MainWindow(iMainWindow *d, iRect rect) {[m }[m #endif[m #if defined (iPlatformAppleMobile)[m [31m- setupWindow_iOS(d);[m [32m+[m[32m setupWindow_iOS(as_Window(d));[m #endif[m [31m- init_Text(d->base.render);[m [32m+[m[32m setCurrent_Text(d->base.text);[m SDL_GetRendererOutputSize(d->base.render, &d->base.size.x, &d->base.size.y); [m setupUserInterface_MainWindow(d);[m postCommand_App("~bindings.changed"); /* update from bindings */[m [36m@@ -538,24 +563,15 @@[m [mvoid init_MainWindow(iMainWindow *d, iRect rect) {[m #endif[m }[m [m [31m-static void deinitRoots_Window_(iWindow *d) {[m [31m- iRecycle();[m [31m- iForIndices(i, d->roots) {[m [31m- if (d->roots[i]) {[m [31m- setCurrent_Root(d->roots[i]);[m [31m- deinit_Root(d->roots[i]);[m [31m- }[m [31m- }[m [31m- setCurrent_Root(NULL);[m [31m-}[m [31m-[m void deinit_MainWindow(iMainWindow *d) {[m deinitRoots_Window_(as_Window(d));[m if (theWindow_ == as_Window(d)) {[m theWindow_ = NULL;[m }[m [32m+[m[32m if (theMainWindow_ == d) {[m [32m+[m[32m theMainWindow_ = NULL;[m [32m+[m[32m }[m delete_String(d->pendingSplitUrl);[m [31m- deinit_Text();[m deinit_Window(&d->base);[m }[m [m [36m@@ -592,7 +608,7 @@[m [miRoot *otherRoot_Window(const iWindow *d, iRoot *root) {[m static void invalidate_MainWindow_(iMainWindow *d, iBool forced) {[m if (d && (!d->base.isInvalidated || forced)) {[m d->base.isInvalidated = iTrue;[m [31m- resetFonts_Text();[m [32m+[m[32m resetFonts_Text(text_Window(d));[m postCommand_App("theme.changed auto:1"); /* forces UI invalidation */[m }[m }[m [36m@@ -607,7 +623,7 @@[m [mvoid invalidate_Window(iAnyWindow *d) {[m }[m [m static iBool isNormalPlacement_MainWindow_(const iMainWindow *d) {[m [31m- if (d->base.isDrawFrozen) return iFalse;[m [32m+[m[32m if (d->isDrawFrozen) return iFalse;[m #if defined (iPlatformApple)[m /* Maximized mode is not special on macOS. */[m if (snap_MainWindow(d) == maximized_WindowSnap) {[m [36m@@ -655,7 +671,7 @@[m [mstatic iBool unsnap_MainWindow_(iMainWindow *d, const iInt2 *newPos) {[m static void notifyMetricsChange_Window_(const iWindow *d) {[m /* Dynamic UI metrics change. Widgets need to update themselves. */[m setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale);[m [31m- resetFonts_Text();[m [32m+[m[32m resetFonts_Text(d->text);[m postCommand_App("metrics.changed");[m }[m [m [36m@@ -676,6 +692,41 @@[m [mstatic void checkPixelRatioChange_Window_(iWindow *d) {[m }[m }[m [m [32m+[m[32mstatic iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {[m [32m+[m[32m if (ev->windowID != SDL_GetWindowID(d->win)) {[m [32m+[m[32m return iFalse;[m [32m+[m[32m }[m [32m+[m[32m switch (ev->event) {[m [32m+[m[32m case SDL_WINDOWEVENT_EXPOSED:[m [32m+[m[32m d->isExposed = iTrue;[m [32m+[m[32m postRefresh_App();[m [32m+[m[32m return iTrue;[m [32m+[m[32m case SDL_WINDOWEVENT_RESTORED:[m [32m+[m[32m case SDL_WINDOWEVENT_SHOWN:[m [32m+[m[32m postRefresh_App();[m [32m+[m[32m return iTrue;[m [32m+[m[32m case SDL_WINDOWEVENT_FOCUS_LOST:[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 return iTrue;[m [32m+[m[32m case SDL_WINDOWEVENT_LEAVE:[m [32m+[m[32m unhover_Widget();[m [32m+[m[32m d->isMouseInside = iFalse;[m [32m+[m[32m //postCommand_App("window.mouse.exited");[m [32m+[m[32m// SDL_SetWindowInputFocus(mainWindow_App()->base.win);[m [32m+[m[32m printf("mouse leaves popup\n"); fflush(stdout);[m [32m+[m[32m //SDL_RaiseWindow(mainWindow_App()->base.win);[m [32m+[m[32m postRefresh_App();[m [32m+[m[32m return iTrue;[m [32m+[m[32m case SDL_WINDOWEVENT_ENTER:[m [32m+[m[32m d->isMouseInside = iTrue;[m [32m+[m[32m //postCommand_App("window.mouse.entered");[m [32m+[m[32m printf("mouse enters popup\n"); fflush(stdout);[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m [32m+[m[32m return iFalse;[m [32m+[m[32m}[m [32m+[m static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) {[m switch (ev->event) {[m #if defined(iPlatformDesktop)[m [36m@@ -795,6 +846,7 @@[m [mstatic iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent[m return iTrue;[m case SDL_WINDOWEVENT_ENTER:[m d->base.isMouseInside = iTrue;[m [32m+[m[32m SDL_SetWindowInputFocus(d->base.win);[m postCommand_App("window.mouse.entered");[m return iTrue;[m case SDL_WINDOWEVENT_FOCUS_GAINED:[m [36m@@ -802,16 +854,16 @@[m [mstatic iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent[m setCapsLockDown_Keys(iFalse);[m postCommand_App("window.focus.gained");[m d->base.isExposed = iTrue;[m [31m-#if !defined(iPlatformDesktop)[m [32m+[m[32m#if !defined (iPlatformDesktop)[m /* Returned to foreground, may have lost buffered content. */[m [31m- invalidate_Window_(d, iTrue);[m [32m+[m[32m invalidate_MainWindow_(d, iTrue);[m postCommand_App("window.unfreeze");[m #endif[m return iFalse;[m case SDL_WINDOWEVENT_FOCUS_LOST:[m postCommand_App("window.focus.lost");[m [31m-#if !defined(iPlatformDesktop)[m [31m- setFreezeDraw_Window(d, iTrue);[m [32m+[m[32m#if !defined (iPlatformDesktop)[m [32m+[m[32m setFreezeDraw_MainWindow(d, iTrue);[m #endif[m return iFalse;[m case SDL_WINDOWEVENT_TAKE_FOCUS:[m [36m@@ -831,8 +883,8 @@[m [mstatic void applyCursor_Window_(iWindow *d) {[m }[m }[m [m [31m-iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {[m [31m- iWindow *w = as_Window(d);[m [32m+[m[32miBool processEvent_Window(iWindow *d, const SDL_Event *ev) {[m [32m+[m[32m iMainWindow *mw = (type_Window(d) == main_WindowType ? as_MainWindow(d) : NULL);[m switch (ev->type) {[m #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)[m case SDL_SYSWMEVENT: {[m [36m@@ -845,19 +897,26 @@[m [miBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {[m }[m #endif[m case SDL_WINDOWEVENT: {[m [31m- return handleWindowEvent_MainWindow_(d, &ev->window);[m [32m+[m[32m if (mw) {[m [32m+[m[32m return handleWindowEvent_MainWindow_(mw, &ev->window);[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m return handleWindowEvent_Window_(d, &ev->window);[m [32m+[m[32m }[m }[m case SDL_RENDER_TARGETS_RESET:[m case SDL_RENDER_DEVICE_RESET: {[m [31m- invalidate_MainWindow_(d, iTrue /* force full reset */);[m [32m+[m[32m if (mw) {[m [32m+[m[32m invalidate_MainWindow_(mw, iTrue /* force full reset */);[m [32m+[m[32m }[m break;[m }[m default: {[m SDL_Event event = *ev;[m if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze")) {[m [31m- d->base.isDrawFrozen = iFalse;[m [31m- if (SDL_GetWindowFlags(w->win) & SDL_WINDOW_HIDDEN) {[m [31m- SDL_ShowWindow(w->win);[m [32m+[m[32m mw->isDrawFrozen = iFalse;[m [32m+[m[32m if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) {[m [32m+[m[32m SDL_ShowWindow(d->win);[m }[m postRefresh_App();[m postCommand_App("media.player.update"); /* in case a player needs updating */[m [36m@@ -866,35 +925,35 @@[m [miBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {[m if (processEvent_Touch(&event)) {[m return iTrue;[m }[m [31m- if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->base.focusGainedAt < 10) {[m [32m+[m[32m if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->focusGainedAt < 10) {[m /* Suspiciously close to when input focus was received. For example under openbox,[m closing xterm with Ctrl+D will cause the keydown event to "spill" over to us.[m As a workaround, ignore these events. */[m return iTrue; /* won't go to bindings, either */[m }[m [31m- if (event.type == SDL_MOUSEBUTTONDOWN && d->base.ignoreClick) {[m [31m- d->base.ignoreClick = iFalse;[m [32m+[m[32m if (event.type == SDL_MOUSEBUTTONDOWN && d->ignoreClick) {[m [32m+[m[32m d->ignoreClick = iFalse;[m return iTrue;[m }[m /* Map mouse pointer coordinate to our coordinate system. */[m if (event.type == SDL_MOUSEMOTION) {[m [31m- setCursor_Window(w, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */[m [31m- const iInt2 pos = coord_Window(w, event.motion.x, event.motion.y);[m [32m+[m[32m setCursor_Window(d, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */[m [32m+[m[32m const iInt2 pos = coord_Window(d, event.motion.x, event.motion.y);[m event.motion.x = pos.x;[m event.motion.y = pos.y;[m }[m else if (event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {[m [31m- const iInt2 pos = coord_Window(w, event.button.x, event.button.y);[m [32m+[m[32m const iInt2 pos = coord_Window(d, event.button.x, event.button.y);[m event.button.x = pos.x;[m event.button.y = pos.y;[m if (event.type == SDL_MOUSEBUTTONDOWN) {[m /* Button clicks will change keyroot. */[m [31m- if (numRoots_Window(w) > 1) {[m [32m+[m[32m if (numRoots_Window(d) > 1) {[m const iInt2 click = init_I2(event.button.x, event.button.y);[m [31m- iForIndices(i, w->roots) {[m [31m- iRoot *root = w->roots[i];[m [31m- if (root != w->keyRoot && contains_Rect(rect_Root(root), click)) {[m [31m- setKeyRoot_Window(w, root);[m [32m+[m[32m iForIndices(i, d->roots) {[m [32m+[m[32m iRoot *root = d->roots[i];[m [32m+[m[32m if (root != d->keyRoot && contains_Rect(rect_Root(root), click)) {[m [32m+[m[32m setKeyRoot_Window(d, root);[m break;[m }[m }[m [36m@@ -909,13 +968,13 @@[m [miBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {[m event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {[m if (mouseGrab_Widget()) {[m iWidget *grabbed = mouseGrab_Widget();[m [31m- setCurrent_Root(findRoot_Window(w, grabbed));[m [32m+[m[32m setCurrent_Root(findRoot_Window(d, grabbed));[m wasUsed = dispatchEvent_Widget(grabbed, &event);[m }[m }[m /* Dispatch the event to the tree of widgets. */[m if (!wasUsed) {[m [31m- wasUsed = dispatchEvent_Window(w, &event);[m [32m+[m[32m wasUsed = dispatchEvent_Window(d, &event);[m }[m if (!wasUsed) {[m /* As a special case, clicking the middle mouse button can be used for pasting[m [36m@@ -928,35 +987,35 @@[m [miBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {[m paste.key.keysym.mod = KMOD_PRIMARY;[m paste.key.state = SDL_PRESSED;[m paste.key.timestamp = SDL_GetTicks();[m [31m- wasUsed = dispatchEvent_Window(w, &paste);[m [32m+[m[32m wasUsed = dispatchEvent_Window(d, &paste);[m }[m if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) {[m [31m- if (postContextClick_Window(w, &event.button)) {[m [32m+[m[32m if (postContextClick_Window(d, &event.button)) {[m wasUsed = iTrue;[m }[m }[m }[m if (isMetricsChange_UserEvent(&event)) {[m [31m- iForIndices(i, w->roots) {[m [31m- updateMetrics_Root(w->roots[i]);[m [32m+[m[32m iForIndices(i, d->roots) {[m [32m+[m[32m updateMetrics_Root(d->roots[i]);[m }[m }[m [31m- if (isCommand_UserEvent(&event, "lang.changed")) {[m [32m+[m[32m if (isCommand_UserEvent(&event, "lang.changed") && mw) {[m #if defined (iHaveNativeMenus)[m /* Retranslate the menus. */[m removeMacMenus_();[m insertMacMenus_();[m #endif[m [31m- invalidate_Window(w);[m [31m- iForIndices(i, w->roots) {[m [31m- if (w->roots[i]) {[m [31m- updatePreferencesLayout_Widget(findChild_Widget(w->roots[i]->widget, "prefs"));[m [31m- arrange_Widget(w->roots[i]->widget);[m [32m+[m[32m invalidate_Window(d);[m [32m+[m[32m iForIndices(i, d->roots) {[m [32m+[m[32m if (d->roots[i]) {[m [32m+[m[32m updatePreferencesLayout_Widget(findChild_Widget(d->roots[i]->widget, "prefs"));[m [32m+[m[32m arrange_Widget(d->roots[i]->widget);[m }[m }[m }[m if (event.type == SDL_MOUSEMOTION) {[m [31m- applyCursor_Window_(w);[m [32m+[m[32m applyCursor_Window_(d);[m }[m return wasUsed;[m }[m [36m@@ -1003,6 +1062,9 @@[m [miBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) {[m coord_MouseWheelEvent(&ev->wheel))) {[m continue; /* Only process the event in the relevant split. */[m }[m [32m+[m[32m if (!root->widget) {[m [32m+[m[32m continue;[m [32m+[m[32m }[m setCurrent_Root(root);[m const iBool wasUsed = dispatchEvent_Widget(root->widget, ev);[m if (wasUsed) {[m [36m@@ -1044,11 +1106,40 @@[m [miBool postContextClick_Window(iWindow *d, const SDL_MouseButtonEvent *ev) {[m return iFalse;[m }[m [m [32m+[m[32mvoid draw_Window(iWindow *d) {[m [32m+[m[32m if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) {[m [32m+[m[32m return;[m [32m+[m[32m }[m [32m+[m[32m iPaint p;[m [32m+[m[32m init_Paint(&p);[m [32m+[m[32m iRoot *root = d->roots[0];[m [32m+[m[32m setCurrent_Root(root);[m [32m+[m[32m unsetClip_Paint(&p); /* update clip to full window */[m [32m+[m[32m const iColor back = get_Color(uiBackground_ColorId);[m [32m+[m[32m SDL_SetRenderDrawColor(d->render, back.r, back.g, back.b, 255);[m [32m+[m[32m SDL_RenderClear(d->render);[m [32m+[m[32m d->frameTime = SDL_GetTicks();[m [32m+[m[32m if (isExposed_Window(d)) {[m [32m+[m[32m d->isInvalidated = iFalse;[m [32m+[m[32m extern int drawCount_;[m [32m+[m[32m drawRoot_Widget(root->widget);[m [32m+[m[32m#if !defined (NDEBUG)[m [32m+[m[32m draw_Text(defaultBold_FontId, safeRect_Root(root).pos, red_ColorId, "%d", drawCount_);[m [32m+[m[32m drawCount_ = 0;[m [32m+[m[32m#endif[m[41m [m [32m+[m[32m }[m [32m+[m[32m// drawRectThickness_Paint(&p, (iRect){ zero_I2(), sub_I2(d->size, one_I2()) }, gap_UI / 4, uiSeparator_ColorId);[m [32m+[m[32m setCurrent_Root(NULL);[m [32m+[m[32m SDL_RenderPresent(d->render);[m [32m+[m[32m}[m [32m+[m void draw_MainWindow(iMainWindow *d) {[m [32m+[m[32m /* TODO: Try to make this a specialization of `draw_Window`? */[m iWindow *w = as_Window(d);[m [31m- if (w->isDrawFrozen) {[m [32m+[m[32m if (d->isDrawFrozen) {[m return;[m }[m [32m+[m[32m setCurrent_Text(d->base.text);[m /* Check if root needs resizing. */ {[m iInt2 renderSize;[m SDL_GetRendererOutputSize(w->render, &renderSize.x, &renderSize.y);[m [36m@@ -1180,7 +1271,7 @@[m [mvoid setUiScale_Window(iWindow *d, float uiScale) {[m }[m }[m [m [31m-void setFreezeDraw_Window(iWindow *d, iBool freezeDraw) {[m [32m+[m[32mvoid setFreezeDraw_MainWindow(iMainWindow *d, iBool freezeDraw) {[m d->isDrawFrozen = freezeDraw;[m }[m [m [36m@@ -1231,8 +1322,23 @@[m [miWindow *get_Window(void) {[m return theWindow_;[m }[m [m [32m+[m[32mvoid setCurrent_Window(iAnyWindow *d) {[m [32m+[m[32m theWindow_ = d;[m [32m+[m[32m if (type_Window(d) == main_WindowType) {[m [32m+[m[32m theMainWindow_ = d;[m [32m+[m[32m }[m [32m+[m[32m if (d) {[m [32m+[m[32m setCurrent_Text(theWindow_->text);[m [32m+[m[32m setCurrent_Root(theWindow_->keyRoot);[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m setCurrent_Text(NULL);[m [32m+[m[32m setCurrent_Root(NULL);[m [32m+[m[32m }[m [32m+[m[32m}[m [32m+[m iMainWindow *get_MainWindow(void) {[m [31m- return as_MainWindow(theWindow_);[m [32m+[m[32m return theMainWindow_;[m }[m [m iBool isOpenGLRenderer_Window(void) {[m [36m@@ -1272,7 +1378,7 @@[m [mvoid setSplitMode_MainWindow(iMainWindow *d, int splitFlags) {[m iAssert(current_Root() == NULL);[m if (d->splitMode != splitMode) {[m int oldCount = numRoots_Window(w);[m [31m- setFreezeDraw_Window(w, iTrue);[m [32m+[m[32m setFreezeDraw_MainWindow(d, iTrue);[m if (oldCount == 2 && splitMode == 0) {[m /* Keep references to the tabs of the second root. */[m const iDocumentWidget *curPage = document_Root(w->keyRoot);[m [36m@@ -1311,6 +1417,7 @@[m [mvoid setSplitMode_MainWindow(iMainWindow *d, int splitFlags) {[m }[m w->roots[newRootIndex] = new_Root();[m w->keyRoot = w->roots[newRootIndex];[m [32m+[m[32m w->keyRoot->window = w;[m setCurrent_Root(w->roots[newRootIndex]);[m createUserInterface_Root(w->roots[newRootIndex]);[m if (!isEmpty_String(d->pendingSplitUrl)) {[m [36m@@ -1471,3 +1578,25 @@[m [mint snap_MainWindow(const iMainWindow *d) {[m }[m return d->place.snap;[m }[m [32m+[m [32m+[m[32m/*----------------------------------------------------------------------------------------------*/[m [32m+[m [32m+[m[32miWindow *newPopup_Window(iInt2 screenPos, iWidget *rootWidget) {[m [32m+[m[32m arrange_Widget(rootWidget);[m [32m+[m[32m iWindow *win =[m [32m+[m[32m new_Window(popup_WindowType,[m [32m+[m[32m (iRect){ screenPos, divf_I2(rootWidget->rect.size, get_Window()->pixelRatio) },[m [32m+[m[32m SDL_WINDOW_ALWAYS_ON_TOP |[m [32m+[m[32m SDL_WINDOW_POPUP_MENU |[m [32m+[m[32m SDL_WINDOW_SKIP_TASKBAR);[m [32m+[m[32m#if defined (iPlatformAppleDesktop)[m [32m+[m[32m hideTitleBar_MacOS(win); /* make it a borderless window */[m [32m+[m[32m#endif[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 [32m+[m[32m setRoot_Widget(rootWidget, root);[m [32m+[m[32m return win;[m [32m+[m[32m}[m [1mdiff --git a/src/ui/window.h b/src/ui/window.h[m [1mindex 73e92391..f1827931 100644[m [1m--- a/src/ui/window.h[m [1m+++ b/src/ui/window.h[m [36m@@ -29,8 +29,16 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m #include [m #include [m [m [32m+[m[32menum iWindowType {[m [32m+[m[32m main_WindowType,[m [32m+[m[32m popup_WindowType,[m [32m+[m[32m};[m [32m+[m iDeclareType(MainWindow)[m [32m+[m[32miDeclareType(Text)[m iDeclareType(Window)[m [32m+[m[41m [m [32m+[m[32miDeclareTypeConstructionArgs(Window, enum iWindowType type, iRect rect, uint32_t flags)[m iDeclareTypeConstructionArgs(MainWindow, iRect rect)[m [m typedef iAny iAnyWindow;[m [36m@@ -71,15 +79,9 @@[m [menum iWindowSplit {[m noEvents_WindowSplit = iBit(11),[m };[m [m [31m-enum iWindowType {[m [31m- main_WindowType,[m [31m- popup_WindowType,[m [31m-};[m [31m-[m struct Impl_Window {[m enum iWindowType type;[m SDL_Window * win;[m [31m- iBool isDrawFrozen; /* avoids premature draws while restoring window state */[m iBool isExposed;[m iBool isMinimized;[m iBool isMouseInside;[m [36m@@ -102,11 +104,13 @@[m [mstruct Impl_Window {[m iRoot * roots[2]; /* root widget and UI state; second one is for split mode */[m iRoot * keyRoot; /* root that has the current keyboard input focus */[m SDL_Texture * borderShadow;[m [32m+[m[32m iText * text;[m };[m [m struct Impl_MainWindow {[m iWindow base;[m iWindowPlacement place;[m [32m+[m[32m iBool isDrawFrozen; /* avoids premature draws while restoring window state */[m int splitMode;[m int pendingSplitMode;[m iString * pendingSplitUrl; /* URL to open in a newly opened split */[m [36m@@ -115,7 +119,10 @@[m [mstruct Impl_MainWindow {[m };[m [m iLocalDef enum iWindowType type_Window(const iAnyWindow *d) {[m [31m- return ((const iWindow *) d)->type;[m [32m+[m[32m if (d) {[m [32m+[m[32m return ((const iWindow *) d)->type;[m [32m+[m[32m }[m [32m+[m[32m return main_WindowType;[m }[m [m uint32_t id_Window (const iWindow *);[m [36m@@ -131,11 +138,11 @@[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[32miBool processEvent_Window (iWindow *, const SDL_Event *);[m iBool dispatchEvent_Window (iWindow *, const SDL_Event *);[m void invalidate_Window (iAnyWindow *); /* discard all cached graphics */[m void draw_Window (iWindow *);[m void setUiScale_Window (iWindow *, float uiScale);[m [31m-void setFreezeDraw_Window (iWindow *, iBool freezeDraw);[m void setCursor_Window (iWindow *, int cursor);[m iBool setKeyRoot_Window (iWindow *, iRoot *root);[m iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *);[m [36m@@ -143,6 +150,8 @@[m [miBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *);[m iWindow * get_Window (void);[m iBool isOpenGLRenderer_Window (void);[m [m [32m+[m[32mvoid setCurrent_Window (iAnyWindow *);[m [32m+[m iLocalDef iBool isExposed_Window(const iWindow *d) {[m iAssert(d);[m return d->isExposed;[m [36m@@ -158,6 +167,10 @@[m [miLocalDef const iWindow *constAs_Window(const iAnyWindow *d) {[m return (const iWindow *) d;[m }[m [m [32m+[m[32miLocalDef iText *text_Window(const iAnyWindow *d) {[m [32m+[m[32m return constAs_Window(d)->text;[m [32m+[m[32m}[m [32m+[m /*----------------------------------------------------------------------------------------------*/[m [m iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) {[m [36m@@ -167,6 +180,7 @@[m [miLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) {[m [m void setTitle_MainWindow (iMainWindow *, const iString *title);[m void setSnap_MainWindow (iMainWindow *, int snapMode);[m [32m+[m[32mvoid setFreezeDraw_MainWindow (iMainWindow *, iBool freezeDraw);[m void setKeyboardHeight_MainWindow (iMainWindow *, int height);[m void setSplitMode_MainWindow (iMainWindow *, int splitMode);[m void checkPendingSplit_MainWindow (iMainWindow *);[m [36m@@ -196,3 +210,7 @@[m [miLocalDef const iMainWindow *constAs_MainWindow(const iAnyWindow *d) {[m iAssert(type_Window(d) == main_WindowType);[m return (const iMainWindow *) d;[m }[m [32m+[m [32m+[m[32m/*----------------------------------------------------------------------------------------------*/[m [32m+[m [32m+[m[32miWindow * newPopup_Window (iInt2 screenPos, iWidget *rootWidget);[m
text/gemini; charset=utf-8
This content has been proxied by September (ba2dc).