summaryrefslogtreecommitdiff
path: root/src/ui/window.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/window.c')
-rw-r--r--src/ui/window.c732
1 files changed, 458 insertions, 274 deletions
diff --git a/src/ui/window.c b/src/ui/window.c
index f8391ed9..066ea102 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
30#include "keys.h" 30#include "keys.h"
31#include "labelwidget.h" 31#include "labelwidget.h"
32#include "documentwidget.h" 32#include "documentwidget.h"
33#include "sidebarwidget.h"
33#include "paint.h" 34#include "paint.h"
34#include "root.h" 35#include "root.h"
35#include "touch.h" 36#include "touch.h"
@@ -57,7 +58,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
57#include "stb_image.h" 58#include "stb_image.h"
58#include "stb_image_resize.h" 59#include "stb_image_resize.h"
59 60
60static iWindow *theWindow_ = NULL; 61static iWindow * theWindow_;
62static iMainWindow *theMainWindow_;
61 63
62#if defined (iPlatformApple) || defined (iPlatformLinux) || defined (iPlatformOther) 64#if defined (iPlatformApple) || defined (iPlatformLinux) || defined (iPlatformOther)
63static float initialUiScale_ = 1.0f; 65static float initialUiScale_ = 1.0f;
@@ -67,7 +69,10 @@ static float initialUiScale_ = 1.1f;
67 69
68static iBool isOpenGLRenderer_; 70static iBool isOpenGLRenderer_;
69 71
70iDefineTypeConstructionArgs(Window, (iRect rect), rect) 72iDefineTypeConstructionArgs(Window,
73 (enum iWindowType type, iRect rect, uint32_t flags),
74 type, rect, flags)
75iDefineTypeConstructionArgs(MainWindow, (iRect rect), rect)
71 76
72/* TODO: Define menus per platform. */ 77/* TODO: Define menus per platform. */
73 78
@@ -116,6 +121,7 @@ static const iMenuItem viewMenuItems_[] = {
116static iMenuItem bookmarksMenuItems_[] = { 121static iMenuItem bookmarksMenuItems_[] = {
117 { "${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, 122 { "${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" },
118 { "${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, 123 { "${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" },
124 { "${menu.newfolder}", 0, 0, "bookmarks.addfolder" },
119 { "---", 0, 0, NULL }, 125 { "---", 0, 0, NULL },
120 { "${menu.import.links}", 0, 0, "bookmark.links confirm:1" }, 126 { "${menu.import.links}", 0, 0, "bookmark.links confirm:1" },
121 { "---", 0, 0, NULL }, 127 { "---", 0, 0, NULL },
@@ -124,6 +130,8 @@ static iMenuItem bookmarksMenuItems_[] = {
124 { "${macos.menu.bookmarks.bytime}", 0, 0, "open url:about:bookmarks?created" }, 130 { "${macos.menu.bookmarks.bytime}", 0, 0, "open url:about:bookmarks?created" },
125 { "${menu.feeds.entrylist}", 0, 0, "open url:about:feeds" }, 131 { "${menu.feeds.entrylist}", 0, 0, "open url:about:feeds" },
126 { "---", 0, 0, NULL }, 132 { "---", 0, 0, NULL },
133 { "${menu.sort.alpha}", 0, 0, "bookmarks.sort" },
134 { "---", 0, 0, NULL },
127 { "${menu.bookmarks.refresh}", 0, 0, "bookmarks.reload.remote" }, 135 { "${menu.bookmarks.refresh}", 0, 0, "bookmarks.reload.remote" },
128 { "${menu.feeds.refresh}", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" }, 136 { "${menu.feeds.refresh}", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" },
129}; 137};
@@ -169,17 +177,17 @@ int numRoots_Window(const iWindow *d) {
169 return num; 177 return num;
170} 178}
171 179
172static void windowSizeChanged_Window_(iWindow *d) { 180static void windowSizeChanged_MainWindow_(iMainWindow *d) {
173 const int numRoots = numRoots_Window(d); 181 const int numRoots = numRoots_Window(as_Window(d));
174 const iInt2 rootSize = d->size; 182 const iInt2 rootSize = d->base.size;
175 const int weights[2] = { 183 const int weights[2] = {
176 d->roots[0] ? (d->splitMode & twoToOne_WindowSplit ? 2 : 1) : 0, 184 d->base.roots[0] ? (d->splitMode & twoToOne_WindowSplit ? 2 : 1) : 0,
177 d->roots[1] ? (d->splitMode & oneToTwo_WindowSplit ? 2 : 1) : 0, 185 d->base.roots[1] ? (d->splitMode & oneToTwo_WindowSplit ? 2 : 1) : 0,
178 }; 186 };
179 const int totalWeight = weights[0] + weights[1]; 187 const int totalWeight = weights[0] + weights[1];
180 int w = 0; 188 int w = 0;
181 iForIndices(i, d->roots) { 189 iForIndices(i, d->base.roots) {
182 iRoot *root = d->roots[i]; 190 iRoot *root = d->base.roots[i];
183 if (root) { 191 if (root) {
184 iRect *rect = &root->widget->rect; 192 iRect *rect = &root->widget->rect;
185 /* Horizontal split frame. */ 193 /* Horizontal split frame. */
@@ -199,26 +207,27 @@ static void windowSizeChanged_Window_(iWindow *d) {
199 } 207 }
200} 208}
201 209
202static void setupUserInterface_Window(iWindow *d) { 210static void setupUserInterface_MainWindow(iMainWindow *d) {
203#if defined (iHaveNativeMenus) 211#if defined (iHaveNativeMenus)
204 insertMacMenus_(); 212 insertMacMenus_();
205#endif 213#endif
206 /* One root is created by default. */ 214 /* One root is created by default. */
207 d->roots[0] = new_Root(); 215 d->base.roots[0] = new_Root();
208 setCurrent_Root(d->roots[0]); 216 d->base.roots[0]->window = as_Window(d);
209 createUserInterface_Root(d->roots[0]); 217 setCurrent_Root(d->base.roots[0]);
218 createUserInterface_Root(d->base.roots[0]);
210 setCurrent_Root(NULL); 219 setCurrent_Root(NULL);
211 /* One of the roots always has keyboard input focus. */ 220 /* One of the roots always has keyboard input focus. */
212 d->keyRoot = d->roots[0]; 221 d->base.keyRoot = d->base.roots[0];
213} 222}
214 223
215static void updateSize_Window_(iWindow *d, iBool notifyAlways) { 224static void updateSize_MainWindow_(iMainWindow *d, iBool notifyAlways) {
216 iInt2 *size = &d->size; 225 iInt2 *size = &d->base.size;
217 const iInt2 oldSize = *size; 226 const iInt2 oldSize = *size;
218 SDL_GetRendererOutputSize(d->render, &size->x, &size->y); 227 SDL_GetRendererOutputSize(d->base.render, &size->x, &size->y);
219 size->y -= d->keyboardHeight; 228 size->y -= d->keyboardHeight;
220 if (notifyAlways || !isEqual_I2(oldSize, *size)) { 229 if (notifyAlways || !isEqual_I2(oldSize, *size)) {
221 windowSizeChanged_Window_(d); 230 windowSizeChanged_MainWindow_(d);
222 if (!isEqual_I2(*size, d->place.lastNotifiedSize)) { 231 if (!isEqual_I2(*size, d->place.lastNotifiedSize)) {
223 const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x); 232 const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x);
224 const iBool isVert = (d->place.lastNotifiedSize.y != size->y); 233 const iBool isVert = (d->place.lastNotifiedSize.y != size->y);
@@ -234,8 +243,8 @@ static void updateSize_Window_(iWindow *d, iBool notifyAlways) {
234 } 243 }
235} 244}
236 245
237void drawWhileResizing_Window(iWindow *d, int w, int h) { 246void drawWhileResizing_MainWindow(iMainWindow *d, int w, int h) {
238 draw_Window(d); 247 draw_MainWindow(d);
239} 248}
240 249
241static float pixelRatio_Window_(const iWindow *d) { 250static float pixelRatio_Window_(const iWindow *d) {
@@ -308,7 +317,7 @@ static iRoot *rootAt_Window_(const iWindow *d, iInt2 coord) {
308} 317}
309 318
310#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 319#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
311static SDL_HitTestResult hitTest_Window_(SDL_Window *win, const SDL_Point *pos, void *data) { 320static SDL_HitTestResult hitTest_MainWindow_(SDL_Window *win, const SDL_Point *pos, void *data) {
312 iWindow *d = data; 321 iWindow *d = data;
313 iAssert(d->win == win); 322 iAssert(d->win == win);
314 if (SDL_GetWindowFlags(d->win) & (SDL_WINDOW_MOUSE_CAPTURE | SDL_WINDOW_FULLSCREEN_DESKTOP)) { 323 if (SDL_GetWindowFlags(d->win) & (SDL_WINDOW_MOUSE_CAPTURE | SDL_WINDOW_FULLSCREEN_DESKTOP)) {
@@ -361,19 +370,22 @@ SDL_HitTestResult hitTest_Window(const iWindow *d, iInt2 pos) {
361#endif 370#endif
362 371
363iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) { 372iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) {
364 flags |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN; 373 flags |= SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN;
374 if (d->type == main_WindowType) {
375 flags |= SDL_WINDOW_RESIZABLE;
365#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 376#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
366 if (prefs_App()->customFrame) { 377 if (prefs_App()->customFrame) {
367 /* We are drawing a custom frame so hide the default one. */ 378 /* We are drawing a custom frame so hide the default one. */
368 flags |= SDL_WINDOW_BORDERLESS; 379 flags |= SDL_WINDOW_BORDERLESS;
369 } 380 }
370#endif 381#endif
382 }
371 if (SDL_CreateWindowAndRenderer( 383 if (SDL_CreateWindowAndRenderer(
372 width_Rect(rect), height_Rect(rect), flags, &d->win, &d->render)) { 384 width_Rect(rect), height_Rect(rect), flags, &d->win, &d->render)) {
373 return iFalse; 385 return iFalse;
374 } 386 }
375#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 387#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
376 if (prefs_App()->customFrame) { 388 if (type_Window(d) == main_WindowType && prefs_App()->customFrame) {
377 /* Register a handler for window hit testing (drag, resize). */ 389 /* Register a handler for window hit testing (drag, resize). */
378 SDL_SetWindowHitTest(d->win, hitTest_Window_, d); 390 SDL_SetWindowHitTest(d->win, hitTest_Window_, d);
379 SDL_SetWindowResizable(d->win, SDL_TRUE); 391 SDL_SetWindowResizable(d->win, SDL_TRUE);
@@ -397,40 +409,27 @@ static SDL_Surface *loadImage_(const iBlock *data, int resized) {
397 pixels, w, h, 8 * num, w * num, SDL_PIXELFORMAT_RGBA32); 409 pixels, w, h, 8 * num, w * num, SDL_PIXELFORMAT_RGBA32);
398} 410}
399 411
400void init_Window(iWindow *d, iRect rect) { 412void init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags) {
401 theWindow_ = d; 413 d->type = type;
402 d->win = NULL; 414 d->win = NULL;
403 d->size = zero_I2(); /* will be updated below */ 415 d->size = zero_I2(); /* will be updated below */
404 iZap(d->roots); 416 d->hover = NULL;
405 d->splitMode = d->pendingSplitMode = 0; 417 d->lastHover = NULL;
406 d->pendingSplitUrl = new_String(); 418 d->mouseGrab = NULL;
407 d->hover = NULL; 419 d->focus = NULL;
408 d->mouseGrab = NULL;
409 d->focus = NULL;
410 iZap(d->cursors);
411 d->place.initialPos = rect.pos;
412 d->place.normalRect = rect;
413 d->place.lastNotifiedSize = zero_I2();
414 d->place.snap = 0;
415 d->pendingCursor = NULL; 420 d->pendingCursor = NULL;
416 d->isDrawFrozen = iTrue; 421 d->isExposed = iFalse;
417 d->isExposed = iFalse; 422 d->isMinimized = iFalse;
418 d->isMinimized = iFalse;
419 d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */ 423 d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */
420 d->isMouseInside = iTrue; 424 d->isMouseInside = iTrue;
421 d->ignoreClick = iFalse; 425 d->ignoreClick = iFalse;
422 d->focusGainedAt = 0; 426 d->focusGainedAt = 0;
423 d->keyboardHeight = 0; 427 d->presentTime = 0.0;
424 init_Anim(&d->rootOffset, 0.0f); 428 d->frameTime = SDL_GetTicks();
425 uint32_t flags = 0; 429 d->keyRoot = NULL;
426#if defined (iPlatformAppleDesktop) 430 d->borderShadow = NULL;
427 SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); 431 iZap(d->roots);
428#elif defined (iPlatformAppleMobile) 432 iZap(d->cursors);
429 SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal");
430#else
431 flags |= SDL_WINDOW_OPENGL;
432#endif
433 SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
434 /* First try SDL's default renderer that should be the best option. */ 433 /* First try SDL's default renderer that should be the best option. */
435 if (forceSoftwareRender_App() || !create_Window_(d, rect, flags)) { 434 if (forceSoftwareRender_App() || !create_Window_(d, rect, flags)) {
436 /* No luck, maybe software only? This should always work as long as there is a display. */ 435 /* No luck, maybe software only? This should always work as long as there is a display. */
@@ -443,36 +442,95 @@ void init_Window(iWindow *d, iRect rect) {
443 if (left_Rect(rect) >= 0 || top_Rect(rect) >= 0) { 442 if (left_Rect(rect) >= 0 || top_Rect(rect) >= 0) {
444 SDL_SetWindowPosition(d->win, left_Rect(rect), top_Rect(rect)); 443 SDL_SetWindowPosition(d->win, left_Rect(rect), top_Rect(rect));
445 } 444 }
445 SDL_GetRendererOutputSize(d->render, &d->size.x, &d->size.y);
446 drawBlank_Window_(d);
447 d->pixelRatio = pixelRatio_Window_(d); /* point/pixel conversion */
448 d->displayScale = displayScale_Window_(d);
449 d->uiScale = initialUiScale_;
450 /* TODO: Ratios, scales, and metrics must be window-specific, not global. */
451 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale);
452 d->text = new_Text(d->render);
453}
454
455static void deinitRoots_Window_(iWindow *d) {
456 iRecycle();
457 iForIndices(i, d->roots) {
458 if (d->roots[i]) {
459 setCurrent_Root(d->roots[i]);
460 delete_Root(d->roots[i]);
461 d->roots[i] = NULL;
462 }
463 }
464 setCurrent_Root(NULL);
465}
466
467void deinit_Window(iWindow *d) {
468 if (d->type == popup_WindowType) {
469 removePopup_App(d);
470 }
471 deinitRoots_Window_(d);
472 delete_Text(d->text);
473 SDL_DestroyRenderer(d->render);
474 SDL_DestroyWindow(d->win);
475 iForIndices(i, d->cursors) {
476 if (d->cursors[i]) {
477 SDL_FreeCursor(d->cursors[i]);
478 }
479 }
480}
481
482void init_MainWindow(iMainWindow *d, iRect rect) {
483 theWindow_ = &d->base;
484 theMainWindow_ = d;
485 uint32_t flags = 0;
486#if defined (iPlatformAppleDesktop)
487 SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl");
488#elif defined (iPlatformAppleMobile)
489 SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal");
490#else
491 flags |= SDL_WINDOW_OPENGL;
492#endif
493 SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
494 init_Window(&d->base, main_WindowType, rect, flags);
495 d->isDrawFrozen = iTrue;
496 d->splitMode = 0;
497 d->pendingSplitMode = 0;
498 d->pendingSplitUrl = new_String();
499 d->place.initialPos = rect.pos;
500 d->place.normalRect = rect;
501 d->place.lastNotifiedSize = zero_I2();
502 d->place.snap = 0;
503 d->keyboardHeight = 0;
504#if defined(iPlatformMobile)
505 const iInt2 minSize = zero_I2(); /* windows aren't independently resizable */
506#else
446 const iInt2 minSize = init_I2(425, 325); 507 const iInt2 minSize = init_I2(425, 325);
447 SDL_SetWindowMinimumSize(d->win, minSize.x, minSize.y); 508#endif
448 SDL_SetWindowTitle(d->win, "Lagrange"); 509 SDL_SetWindowMinimumSize(d->base.win, minSize.x, minSize.y);
510 SDL_SetWindowTitle(d->base.win, "Lagrange");
449 /* Some info. */ { 511 /* Some info. */ {
450 SDL_RendererInfo info; 512 SDL_RendererInfo info;
451 SDL_GetRendererInfo(d->render, &info); 513 SDL_GetRendererInfo(d->base.render, &info);
452 isOpenGLRenderer_ = !iCmpStr(info.name, "opengl"); 514 isOpenGLRenderer_ = !iCmpStr(info.name, "opengl");
453 printf("[window] renderer: %s%s\n", info.name, 515 printf("[window] renderer: %s%s\n",
516 info.name,
454 info.flags & SDL_RENDERER_ACCELERATED ? " (accelerated)" : ""); 517 info.flags & SDL_RENDERER_ACCELERATED ? " (accelerated)" : "");
455#if !defined (NDEBUG) 518#if !defined(NDEBUG)
456 printf("[window] max texture size: %d x %d\n", 519 printf("[window] max texture size: %d x %d\n",
457 info.max_texture_width, 520 info.max_texture_width,
458 info.max_texture_height); 521 info.max_texture_height);
459 for (size_t i = 0; i < info.num_texture_formats; ++i) { 522 for (size_t i = 0; i < info.num_texture_formats; ++i) {
460 printf("[window] supported texture format: %s\n", SDL_GetPixelFormatName( 523 printf("[window] supported texture format: %s\n",
461 info.texture_formats[i])); 524 SDL_GetPixelFormatName(info.texture_formats[i]));
462 } 525 }
463#endif 526#endif
464 } 527 }
465 drawBlank_Window_(d); 528#if defined(iPlatformMsys)
466 d->pixelRatio = pixelRatio_Window_(d); /* point/pixel conversion */ 529 SDL_SetWindowMinimumSize(d->base.win, minSize.x * d->base.displayScale, minSize.y * d->base.displayScale);
467 d->displayScale = displayScale_Window_(d); 530 useExecutableIconResource_SDLWindow(d->base.win);
468 d->uiScale = initialUiScale_;
469 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale);
470#if defined (iPlatformMsys)
471 SDL_SetWindowMinimumSize(d->win, minSize.x * d->displayScale, minSize.y * d->displayScale);
472 useExecutableIconResource_SDLWindow(d->win);
473#endif 531#endif
474#if defined (iPlatformLinux) 532#if defined (iPlatformLinux)
475 SDL_SetWindowMinimumSize(d->win, minSize.x * d->pixelRatio, minSize.y * d->pixelRatio); 533 SDL_SetWindowMinimumSize(d->win, minSize.x * d->base.pixelRatio, minSize.y * d->base.pixelRatio);
476 /* Load the window icon. */ { 534 /* Load the window icon. */ {
477 SDL_Surface *surf = loadImage_(&imageLagrange64_Embedded, 0); 535 SDL_Surface *surf = loadImage_(&imageLagrange64_Embedded, 0);
478 SDL_SetWindowIcon(d->win, surf); 536 SDL_SetWindowIcon(d->win, surf);
@@ -481,20 +539,16 @@ void init_Window(iWindow *d, iRect rect) {
481 } 539 }
482#endif 540#endif
483#if defined (iPlatformAppleMobile) 541#if defined (iPlatformAppleMobile)
484 setupWindow_iOS(d); 542 setupWindow_iOS(as_Window(d));
485#endif 543#endif
486 d->presentTime = 0.0; 544 setCurrent_Text(d->base.text);
487 d->frameTime = SDL_GetTicks(); 545 SDL_GetRendererOutputSize(d->base.render, &d->base.size.x, &d->base.size.y);
488 d->loadAnimTimer = 0; 546 setupUserInterface_MainWindow(d);
489 init_Text(d->render);
490 SDL_GetRendererOutputSize(d->render, &d->size.x, &d->size.y);
491 setupUserInterface_Window(d);
492 postCommand_App("~bindings.changed"); /* update from bindings */ 547 postCommand_App("~bindings.changed"); /* update from bindings */
493 //updateSize_Window_(d, iFalse);
494 /* Load the border shadow texture. */ { 548 /* Load the border shadow texture. */ {
495 SDL_Surface *surf = loadImage_(&imageShadow_Embedded, 0); 549 SDL_Surface *surf = loadImage_(&imageShadow_Embedded, 0);
496 d->borderShadow = SDL_CreateTextureFromSurface(d->render, surf); 550 d->base.borderShadow = SDL_CreateTextureFromSurface(d->base.render, surf);
497 SDL_SetTextureBlendMode(d->borderShadow, SDL_BLENDMODE_BLEND); 551 SDL_SetTextureBlendMode(d->base.borderShadow, SDL_BLENDMODE_BLEND);
498 free(surf->pixels); 552 free(surf->pixels);
499 SDL_FreeSurface(surf); 553 SDL_FreeSurface(surf);
500 } 554 }
@@ -504,36 +558,26 @@ void init_Window(iWindow *d, iRect rect) {
504 if (prefs_App()->customFrame) { 558 if (prefs_App()->customFrame) {
505 SDL_Surface *surf = loadImage_(&imageLagrange64_Embedded, appIconSize_Root()); 559 SDL_Surface *surf = loadImage_(&imageLagrange64_Embedded, appIconSize_Root());
506 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); 560 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
507 d->appIcon = SDL_CreateTextureFromSurface(d->render, surf); 561 d->appIcon = SDL_CreateTextureFromSurface(d->base.render, surf);
508 free(surf->pixels); 562 free(surf->pixels);
509 SDL_FreeSurface(surf); 563 SDL_FreeSurface(surf);
510 /* We need to observe non-client-area events. */ 564 /* We need to observe non-client-area events. */
511 SDL_EventState(SDL_SYSWMEVENT, SDL_TRUE); 565 SDL_EventState(SDL_SYSWMEVENT, SDL_TRUE);
512 } 566 }
513#endif 567#endif
568 SDL_HideWindow(d->base.win);
514} 569}
515 570
516void deinit_Window(iWindow *d) { 571void deinit_MainWindow(iMainWindow *d) {
517 iRecycle(); 572 deinitRoots_Window_(as_Window(d));
518 iForIndices(i, d->roots) { 573 if (theWindow_ == as_Window(d)) {
519 if (d->roots[i]) {
520 setCurrent_Root(d->roots[i]);
521 deinit_Root(d->roots[i]);
522 }
523 }
524 if (theWindow_ == d) {
525 theWindow_ = NULL; 574 theWindow_ = NULL;
526 } 575 }
527 setCurrent_Root(NULL); 576 if (theMainWindow_ == d) {
528 delete_String(d->pendingSplitUrl); 577 theMainWindow_ = NULL;
529 deinit_Text();
530 SDL_DestroyRenderer(d->render);
531 SDL_DestroyWindow(d->win);
532 iForIndices(i, d->cursors) {
533 if (d->cursors[i]) {
534 SDL_FreeCursor(d->cursors[i]);
535 }
536 } 578 }
579 delete_String(d->pendingSplitUrl);
580 deinit_Window(&d->base);
537} 581}
538 582
539SDL_Renderer *renderer_Window(const iWindow *d) { 583SDL_Renderer *renderer_Window(const iWindow *d) {
@@ -546,8 +590,8 @@ iInt2 maxTextureSize_Window(const iWindow *d) {
546 return init_I2(info.max_texture_width, info.max_texture_height); 590 return init_I2(info.max_texture_width, info.max_texture_height);
547} 591}
548 592
549iBool isFullscreen_Window(const iWindow *d) { 593iBool isFullscreen_MainWindow(const iMainWindow *d) {
550 return snap_Window(d) == fullscreen_WindowSnap; 594 return snap_MainWindow(d) == fullscreen_WindowSnap;
551} 595}
552 596
553iRoot *findRoot_Window(const iWindow *d, const iWidget *widget) { 597iRoot *findRoot_Window(const iWindow *d, const iWidget *widget) {
@@ -566,36 +610,41 @@ iRoot *otherRoot_Window(const iWindow *d, iRoot *root) {
566 return root == d->roots[0] && d->roots[1] ? d->roots[1] : d->roots[0]; 610 return root == d->roots[0] && d->roots[1] ? d->roots[1] : d->roots[0];
567} 611}
568 612
569static void invalidate_Window_(iWindow *d, iBool forced) { 613static void invalidate_MainWindow_(iMainWindow *d, iBool forced) {
570 if (d && (!d->isInvalidated || forced)) { 614 if (d && (!d->base.isInvalidated || forced)) {
571 d->isInvalidated = iTrue; 615 d->base.isInvalidated = iTrue;
572 resetFonts_Text(); 616 resetFonts_Text(text_Window(d));
573 postCommand_App("theme.changed auto:1"); /* forces UI invalidation */ 617 postCommand_App("theme.changed auto:1"); /* forces UI invalidation */
574 } 618 }
575} 619}
576 620
577void invalidate_Window(iWindow *d) { 621void invalidate_Window(iAnyWindow *d) {
578 invalidate_Window_(d, iFalse); 622 if (type_Window(d) == main_WindowType) {
623 invalidate_MainWindow_(as_MainWindow(d), iFalse);
624 }
625 else {
626 iAssert(type_Window(d) == main_WindowType);
627 }
579} 628}
580 629
581static iBool isNormalPlacement_Window_(const iWindow *d) { 630static iBool isNormalPlacement_MainWindow_(const iMainWindow *d) {
582 if (d->isDrawFrozen) return iFalse; 631 if (d->isDrawFrozen) return iFalse;
583#if defined (iPlatformApple) 632#if defined (iPlatformApple)
584 /* Maximized mode is not special on macOS. */ 633 /* Maximized mode is not special on macOS. */
585 if (snap_Window(d) == maximized_WindowSnap) { 634 if (snap_MainWindow(d) == maximized_WindowSnap) {
586 return iTrue; 635 return iTrue;
587 } 636 }
588#endif 637#endif
589 if (snap_Window(d)) return iFalse; 638 if (snap_MainWindow(d)) return iFalse;
590 return !(SDL_GetWindowFlags(d->win) & SDL_WINDOW_MINIMIZED); 639 return !(SDL_GetWindowFlags(d->base.win) & SDL_WINDOW_MINIMIZED);
591} 640}
592 641
593static iBool unsnap_Window_(iWindow *d, const iInt2 *newPos) { 642static iBool unsnap_MainWindow_(iMainWindow *d, const iInt2 *newPos) {
594#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 643#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
595 if (!prefs_App()->customFrame) { 644 if (!prefs_App()->customFrame) {
596 return iFalse; 645 return iFalse;
597 } 646 }
598 const int snap = snap_Window(d); 647 const int snap = snap_MainWindow(d);
599 if (snap == yMaximized_WindowSnap || snap == left_WindowSnap || snap == right_WindowSnap) { 648 if (snap == yMaximized_WindowSnap || snap == left_WindowSnap || snap == right_WindowSnap) {
600 if (!newPos || (d->place.lastHit == SDL_HITTEST_RESIZE_LEFT || 649 if (!newPos || (d->place.lastHit == SDL_HITTEST_RESIZE_LEFT ||
601 d->place.lastHit == SDL_HITTEST_RESIZE_RIGHT)) { 650 d->place.lastHit == SDL_HITTEST_RESIZE_RIGHT)) {
@@ -603,21 +652,21 @@ static iBool unsnap_Window_(iWindow *d, const iInt2 *newPos) {
603 } 652 }
604 if (newPos) { 653 if (newPos) {
605 SDL_Rect usable; 654 SDL_Rect usable;
606 SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->win), &usable); 655 SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->base.win), &usable);
607 /* Snap to top. */ 656 /* Snap to top. */
608 if (snap == yMaximized_WindowSnap && 657 if (snap == yMaximized_WindowSnap &&
609 iAbs(newPos->y - usable.y) < lineHeight_Text(uiContent_FontId) * 2) { 658 iAbs(newPos->y - usable.y) < lineHeight_Text(uiContent_FontId) * 2) {
610 setSnap_Window(d, redo_WindowSnap | yMaximized_WindowSnap); 659 setSnap_MainWindow(d, redo_WindowSnap | yMaximized_WindowSnap);
611 return iFalse; 660 return iFalse;
612 } 661 }
613 } 662 }
614 } 663 }
615 if (snap && snap != fullscreen_WindowSnap) { 664 if (snap && snap != fullscreen_WindowSnap) {
616 if (snap_Window(d) == yMaximized_WindowSnap && newPos) { 665 if (snap_MainWindow(d) == yMaximized_WindowSnap && newPos) {
617 d->place.normalRect.pos = *newPos; 666 d->place.normalRect.pos = *newPos;
618 } 667 }
619 //printf("unsnap\n"); fflush(stdout); 668 //printf("unsnap\n"); fflush(stdout);
620 setSnap_Window(d, none_WindowSnap); 669 setSnap_MainWindow(d, none_WindowSnap);
621 return iTrue; 670 return iTrue;
622 } 671 }
623#endif 672#endif
@@ -627,7 +676,7 @@ static iBool unsnap_Window_(iWindow *d, const iInt2 *newPos) {
627static void notifyMetricsChange_Window_(const iWindow *d) { 676static void notifyMetricsChange_Window_(const iWindow *d) {
628 /* Dynamic UI metrics change. Widgets need to update themselves. */ 677 /* Dynamic UI metrics change. Widgets need to update themselves. */
629 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); 678 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale);
630 resetFonts_Text(); 679 resetFonts_Text(d->text);
631 postCommand_App("metrics.changed"); 680 postCommand_App("metrics.changed");
632} 681}
633 682
@@ -649,146 +698,178 @@ static void checkPixelRatioChange_Window_(iWindow *d) {
649} 698}
650 699
651static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { 700static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
701 if (ev->windowID != SDL_GetWindowID(d->win)) {
702 return iFalse;
703 }
652 switch (ev->event) { 704 switch (ev->event) {
653#if defined (iPlatformDesktop)
654 case SDL_WINDOWEVENT_EXPOSED: 705 case SDL_WINDOWEVENT_EXPOSED:
655 if (!d->isExposed) { 706 d->isExposed = iTrue;
656 drawBlank_Window_(d); /* avoid showing system-provided contents */ 707 postRefresh_App();
657 d->isExposed = iTrue; 708 return iTrue;
658 } 709 case SDL_WINDOWEVENT_RESTORED:
710 case SDL_WINDOWEVENT_SHOWN:
711 postRefresh_App();
712 return iTrue;
713 case SDL_WINDOWEVENT_FOCUS_LOST:
714 /* Popup windows are currently only used for menus. */
715 closeMenu_Widget(d->roots[0]->widget);
716 return iTrue;
717 case SDL_WINDOWEVENT_LEAVE:
718 unhover_Widget();
719 d->isMouseInside = iFalse;
720 //postCommand_App("window.mouse.exited");
721// SDL_SetWindowInputFocus(mainWindow_App()->base.win);
722 printf("mouse leaves popup\n"); fflush(stdout);
723 //SDL_RaiseWindow(mainWindow_App()->base.win);
724 postRefresh_App();
725 return iTrue;
726 case SDL_WINDOWEVENT_ENTER:
727 d->isMouseInside = iTrue;
728 //postCommand_App("window.mouse.entered");
729 printf("mouse enters popup\n"); fflush(stdout);
730 return iTrue;
731 }
732 return iFalse;
733}
734
735static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) {
736 switch (ev->event) {
737#if defined(iPlatformDesktop)
738 case SDL_WINDOWEVENT_EXPOSED:
739 d->base.isExposed = iTrue;
659 /* Since we are manually controlling when to redraw the window, we are responsible 740 /* Since we are manually controlling when to redraw the window, we are responsible
660 for ensuring that window contents get redrawn after expose events. Under certain 741 for ensuring that window contents get redrawn after expose events. Under certain
661 circumstances (e.g., under openbox), not doing this would mean that the window 742 circumstances (e.g., under openbox), not doing this would mean that the window
662 is missing contents until other events trigger a refresh. */ 743 is missing contents until other events trigger a refresh. */
663 postRefresh_App(); 744 postRefresh_App();
664#if defined (LAGRANGE_ENABLE_WINDOWPOS_FIX) 745#if defined(LAGRANGE_ENABLE_WINDOWPOS_FIX)
665 if (d->place.initialPos.x >= 0) { 746 if (d->place.initialPos.x >= 0) {
666 int bx, by; 747 int bx, by;
667 SDL_GetWindowBordersSize(d->win, &by, &bx, NULL, NULL); 748 SDL_GetWindowBordersSize(d->win, &by, &bx, NULL, NULL);
668 SDL_SetWindowPosition(d->win, d->place.initialPos.x + bx, d->place.initialPos.y + by); 749 SDL_SetWindowPosition(
750 d->win, d->place.initialPos.x + bx, d->place.initialPos.y + by);
669 d->place.initialPos = init1_I2(-1); 751 d->place.initialPos = init1_I2(-1);
670 } 752 }
671#endif 753#endif
672 return iFalse; 754 return iFalse;
673 case SDL_WINDOWEVENT_MOVED: { 755 case SDL_WINDOWEVENT_MOVED: {
674 if (d->isMinimized) { 756 if (d->base.isMinimized) {
675 return iFalse; 757 return iFalse;
676 } 758 }
677 checkPixelRatioChange_Window_(d); 759 checkPixelRatioChange_Window_(as_Window(d));
678 const iInt2 newPos = init_I2(ev->data1, ev->data2); 760 const iInt2 newPos = init_I2(ev->data1, ev->data2);
679 if (isEqual_I2(newPos, init1_I2(-32000))) { /* magic! */ 761 if (isEqual_I2(newPos, init1_I2(-32000))) { /* magic! */
680 /* Maybe minimized? Seems like a Windows constant of some kind. */ 762 /* Maybe minimized? Seems like a Windows constant of some kind. */
681 d->isMinimized = iTrue; 763 d->base.isMinimized = iTrue;
682 return iFalse; 764 return iFalse;
683 } 765 }
684#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 766#if defined(LAGRANGE_ENABLE_CUSTOM_FRAME)
685 /* Set the snap position depending on where the mouse cursor is. */ 767 /* Set the snap position depending on where the mouse cursor is. */
686 if (prefs_App()->customFrame) { 768 if (prefs_App()->customFrame) {
687 SDL_Rect usable; 769 SDL_Rect usable;
688 iInt2 mouse = cursor_Win32(); /* SDL is unaware of the current cursor pos */ 770 iInt2 mouse = cursor_Win32(); /* SDL is unaware of the current cursor pos */
689 SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->win), &usable); 771 SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->base.win), &usable);
690 const iBool isTop = iAbs(mouse.y - usable.y) < gap_UI * 20; 772 const iBool isTop = iAbs(mouse.y - usable.y) < gap_UI * 20;
691 const iBool isBottom = iAbs(usable.y + usable.h - mouse.y) < gap_UI * 20; 773 const iBool isBottom = iAbs(usable.y + usable.h - mouse.y) < gap_UI * 20;
692 if (iAbs(mouse.x - usable.x) < gap_UI) { 774 if (iAbs(mouse.x - usable.x) < gap_UI) {
693 setSnap_Window(d, 775 setSnap_MainWindow(d,
694 redo_WindowSnap | left_WindowSnap | 776 redo_WindowSnap | left_WindowSnap |
695 (isTop ? topBit_WindowSnap : 0) | 777 (isTop ? topBit_WindowSnap : 0) |
696 (isBottom ? bottomBit_WindowSnap : 0)); 778 (isBottom ? bottomBit_WindowSnap : 0));
697 return iTrue; 779 return iTrue;
698 } 780 }
699 if (iAbs(mouse.x - usable.x - usable.w) < gap_UI) { 781 if (iAbs(mouse.x - usable.x - usable.w) < gap_UI) {
700 setSnap_Window(d, 782 setSnap_MainWindow(d,
701 redo_WindowSnap | right_WindowSnap | 783 redo_WindowSnap | right_WindowSnap |
702 (isTop ? topBit_WindowSnap : 0) | 784 (isTop ? topBit_WindowSnap : 0) |
703 (isBottom ? bottomBit_WindowSnap : 0)); 785 (isBottom ? bottomBit_WindowSnap : 0));
704 return iTrue; 786 return iTrue;
705 } 787 }
706 if (iAbs(mouse.y - usable.y) < 2) { 788 if (iAbs(mouse.y - usable.y) < 2) {
707 setSnap_Window(d, 789 setSnap_MainWindow(d,
708 redo_WindowSnap | (d->place.lastHit == SDL_HITTEST_RESIZE_TOP 790 redo_WindowSnap | (d->place.lastHit == SDL_HITTEST_RESIZE_TOP
709 ? yMaximized_WindowSnap 791 ? yMaximized_WindowSnap
710 : maximized_WindowSnap)); 792 : maximized_WindowSnap));
711 return iTrue; 793 return iTrue;
712 } 794 }
713 } 795 }
714#endif /* defined LAGRANGE_ENABLE_CUSTOM_FRAME */ 796#endif /* defined LAGRANGE_ENABLE_CUSTOM_FRAME */
715 //printf("MOVED: %d, %d\n", ev->data1, ev->data2); fflush(stdout); 797 if (unsnap_MainWindow_(d, &newPos)) {
716 if (unsnap_Window_(d, &newPos)) {
717 return iTrue; 798 return iTrue;
718 } 799 }
719 if (isNormalPlacement_Window_(d)) { 800 if (isNormalPlacement_MainWindow_(d)) {
720 d->place.normalRect.pos = newPos; 801 d->place.normalRect.pos = newPos;
721 //printf("normal rect set (move)\n"); fflush(stdout); 802 // printf("normal rect set (move)\n"); fflush(stdout);
722 iInt2 border = zero_I2(); 803 iInt2 border = zero_I2();
723#if !defined (iPlatformApple) 804#if !defined(iPlatformApple)
724 SDL_GetWindowBordersSize(d->win, &border.y, &border.x, NULL, NULL); 805 SDL_GetWindowBordersSize(d->base.win, &border.y, &border.x, NULL, NULL);
725#endif 806#endif
726 d->place.normalRect.pos = max_I2(zero_I2(), sub_I2(d->place.normalRect.pos, border)); 807 d->place.normalRect.pos =
808 max_I2(zero_I2(), sub_I2(d->place.normalRect.pos, border));
727 } 809 }
728 return iTrue; 810 return iTrue;
729 } 811 }
730 case SDL_WINDOWEVENT_RESIZED: 812 case SDL_WINDOWEVENT_RESIZED:
731 if (d->isMinimized) { 813 if (d->base.isMinimized) {
732 //updateSize_Window_(d, iTrue); 814 // updateSize_Window_(d, iTrue);
733 return iTrue; 815 return iTrue;
734 } 816 }
735 if (unsnap_Window_(d, NULL)) { 817 if (unsnap_MainWindow_(d, NULL)) {
736 return iTrue; 818 return iTrue;
737 } 819 }
738 if (isNormalPlacement_Window_(d)) { 820 if (isNormalPlacement_MainWindow_(d)) {
739 d->place.normalRect.size = init_I2(ev->data1, ev->data2); 821 d->place.normalRect.size = init_I2(ev->data1, ev->data2);
740 //printf("normal rect set (resize)\n"); fflush(stdout); 822 // printf("normal rect set (resize)\n"); fflush(stdout);
741 } 823 }
742 checkPixelRatioChange_Window_(d); 824 checkPixelRatioChange_Window_(as_Window(d));
743 //updateSize_Window_(d, iTrue /* we were already redrawing during the resize */);
744 postRefresh_App(); 825 postRefresh_App();
745 return iTrue; 826 return iTrue;
746 case SDL_WINDOWEVENT_RESTORED: 827 case SDL_WINDOWEVENT_RESTORED:
747 case SDL_WINDOWEVENT_SHOWN: 828 case SDL_WINDOWEVENT_SHOWN:
748 updateSize_Window_(d, iTrue); 829 updateSize_MainWindow_(d, iTrue);
749 invalidate_Window_(d, iTrue); 830 invalidate_MainWindow_(d, iTrue);
750 d->isMinimized = iFalse; 831 d->base.isMinimized = iFalse;
751 postRefresh_App(); 832 postRefresh_App();
752 return iTrue; 833 return iTrue;
753 case SDL_WINDOWEVENT_MINIMIZED: 834 case SDL_WINDOWEVENT_MINIMIZED:
754 d->isMinimized = iTrue; 835 d->base.isMinimized = iTrue;
836 return iTrue;
837#else /* if defined (!iPlatformDesktop) */
838 case SDL_WINDOWEVENT_RESIZED:
839 /* On mobile, this occurs when the display is rotated. */
840 invalidate_Window(d);
841 postRefresh_App();
755 return iTrue; 842 return iTrue;
756#endif /* defined (iPlatformDesktop) */ 843#endif
757 case SDL_WINDOWEVENT_LEAVE: 844 case SDL_WINDOWEVENT_LEAVE:
758 unhover_Widget(); 845 unhover_Widget();
759 d->isMouseInside = iFalse; 846 d->base.isMouseInside = iFalse;
760 postCommand_App("window.mouse.exited"); 847 postCommand_App("window.mouse.exited");
761 return iTrue; 848 return iTrue;
762 case SDL_WINDOWEVENT_ENTER: 849 case SDL_WINDOWEVENT_ENTER:
763 d->isMouseInside = iTrue; 850 d->base.isMouseInside = iTrue;
851 SDL_SetWindowInputFocus(d->base.win);
764 postCommand_App("window.mouse.entered"); 852 postCommand_App("window.mouse.entered");
765 return iTrue; 853 return iTrue;
766#if defined (iPlatformMobile)
767 case SDL_WINDOWEVENT_RESIZED:
768 /* On mobile, this occurs when the display is rotated. */
769 invalidate_Window(d);
770 postRefresh_App();
771 return iTrue;
772#endif
773 case SDL_WINDOWEVENT_FOCUS_GAINED: 854 case SDL_WINDOWEVENT_FOCUS_GAINED:
774 d->focusGainedAt = SDL_GetTicks(); 855 d->base.focusGainedAt = SDL_GetTicks();
775 setCapsLockDown_Keys(iFalse); 856 setCapsLockDown_Keys(iFalse);
776 postCommand_App("window.focus.gained"); 857 postCommand_App("window.focus.gained");
777 d->isExposed = iTrue; 858 d->base.isExposed = iTrue;
778#if defined (iPlatformMobile) 859#if !defined (iPlatformDesktop)
779 /* Returned to foreground, may have lost buffered content. */ 860 /* Returned to foreground, may have lost buffered content. */
780 invalidate_Window_(d, iTrue); 861 invalidate_MainWindow_(d, iTrue);
781 postCommand_App("window.unfreeze"); 862 postCommand_App("window.unfreeze");
782#endif 863#endif
783 return iFalse; 864 return iFalse;
784 case SDL_WINDOWEVENT_FOCUS_LOST: 865 case SDL_WINDOWEVENT_FOCUS_LOST:
785 postCommand_App("window.focus.lost"); 866 postCommand_App("window.focus.lost");
786#if defined (iPlatformMobile) 867#if !defined (iPlatformDesktop)
787 setFreezeDraw_Window(d, iTrue); 868 setFreezeDraw_MainWindow(d, iTrue);
788#endif 869#endif
789 return iFalse; 870 return iFalse;
790 case SDL_WINDOWEVENT_TAKE_FOCUS: 871 case SDL_WINDOWEVENT_TAKE_FOCUS:
791 SDL_SetWindowInputFocus(d->win); 872 SDL_SetWindowInputFocus(d->base.win);
792 postRefresh_App(); 873 postRefresh_App();
793 return iTrue; 874 return iTrue;
794 default: 875 default:
@@ -805,6 +886,7 @@ static void applyCursor_Window_(iWindow *d) {
805} 886}
806 887
807iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { 888iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
889 iMainWindow *mw = (type_Window(d) == main_WindowType ? as_MainWindow(d) : NULL);
808 switch (ev->type) { 890 switch (ev->type) {
809#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 891#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
810 case SDL_SYSWMEVENT: { 892 case SDL_SYSWMEVENT: {
@@ -817,21 +899,28 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
817 } 899 }
818#endif 900#endif
819 case SDL_WINDOWEVENT: { 901 case SDL_WINDOWEVENT: {
820 return handleWindowEvent_Window_(d, &ev->window); 902 if (mw) {
903 return handleWindowEvent_MainWindow_(mw, &ev->window);
904 }
905 else {
906 return handleWindowEvent_Window_(d, &ev->window);
907 }
821 } 908 }
822 case SDL_RENDER_TARGETS_RESET: 909 case SDL_RENDER_TARGETS_RESET:
823 case SDL_RENDER_DEVICE_RESET: { 910 case SDL_RENDER_DEVICE_RESET: {
824 invalidate_Window_(d, iTrue /* force full reset */); 911 if (mw) {
912 invalidate_MainWindow_(mw, iTrue /* force full reset */);
913 }
825 break; 914 break;
826 } 915 }
827 default: { 916 default: {
828 SDL_Event event = *ev; 917 SDL_Event event = *ev;
829 if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze")) { 918 if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze") && mw) {
830 d->isDrawFrozen = iFalse; 919 mw->isDrawFrozen = iFalse;
831 if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) { 920 if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) {
832 SDL_ShowWindow(d->win); 921 SDL_ShowWindow(d->win);
833 } 922 }
834 postRefresh_App(); 923 draw_MainWindow(mw); /* don't show a frame of placeholder content */
835 postCommand_App("media.player.update"); /* in case a player needs updating */ 924 postCommand_App("media.player.update"); /* in case a player needs updating */
836 return iTrue; 925 return iTrue;
837 } 926 }
@@ -873,7 +962,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
873 } 962 }
874 } 963 }
875 } 964 }
876 const iWidget *oldHover = d->hover; 965// const iWidget *oldHover = d->hover;
877 iBool wasUsed = iFalse; 966 iBool wasUsed = iFalse;
878 /* Dispatch first to the mouse-grabbed widget. */ 967 /* Dispatch first to the mouse-grabbed widget. */
879// iWidget *widget = d->root.widget; 968// iWidget *widget = d->root.widget;
@@ -913,7 +1002,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
913 updateMetrics_Root(d->roots[i]); 1002 updateMetrics_Root(d->roots[i]);
914 } 1003 }
915 } 1004 }
916 if (isCommand_UserEvent(&event, "lang.changed")) { 1005 if (isCommand_UserEvent(&event, "lang.changed") && mw) {
917#if defined (iHaveNativeMenus) 1006#if defined (iHaveNativeMenus)
918 /* Retranslate the menus. */ 1007 /* Retranslate the menus. */
919 removeMacMenus_(); 1008 removeMacMenus_();
@@ -927,9 +1016,6 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
927 } 1016 }
928 } 1017 }
929 } 1018 }
930 if (oldHover != d->hover) {
931 postRefresh_App();
932 }
933 if (event.type == SDL_MOUSEMOTION) { 1019 if (event.type == SDL_MOUSEMOTION) {
934 applyCursor_Window_(d); 1020 applyCursor_Window_(d);
935 } 1021 }
@@ -949,6 +1035,10 @@ iBool setKeyRoot_Window(iWindow *d, iRoot *root) {
949 return iFalse; 1035 return iFalse;
950} 1036}
951 1037
1038iLocalDef iBool isEscapeKeypress_(const SDL_Event *ev) {
1039 return (ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP) && ev->key.keysym.sym == SDLK_ESCAPE;
1040}
1041
952iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) { 1042iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) {
953 if (ev->type == SDL_MOUSEMOTION) { 1043 if (ev->type == SDL_MOUSEMOTION) {
954 /* Hover widget may change. */ 1044 /* Hover widget may change. */
@@ -964,12 +1054,19 @@ iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) {
964 } 1054 }
965 if ((ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP || ev->type == SDL_TEXTINPUT) 1055 if ((ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP || ev->type == SDL_TEXTINPUT)
966 && d->keyRoot != root) { 1056 && d->keyRoot != root) {
967 continue; /* Key events go only to the root with keyboard focus. */ 1057 if (!isEscapeKeypress_(ev)) {
1058 /* Key events go only to the root with keyboard focus, with the exception
1059 of Escape that will also affect the entire window. */
1060 continue;
1061 }
968 } 1062 }
969 if (ev->type == SDL_MOUSEWHEEL && !contains_Rect(rect_Root(root), 1063 if (ev->type == SDL_MOUSEWHEEL && !contains_Rect(rect_Root(root),
970 coord_MouseWheelEvent(&ev->wheel))) { 1064 coord_MouseWheelEvent(&ev->wheel))) {
971 continue; /* Only process the event in the relevant split. */ 1065 continue; /* Only process the event in the relevant split. */
972 } 1066 }
1067 if (!root->widget) {
1068 continue;
1069 }
973 setCurrent_Root(root); 1070 setCurrent_Root(root);
974 const iBool wasUsed = dispatchEvent_Widget(root->widget, ev); 1071 const iBool wasUsed = dispatchEvent_Widget(root->widget, ev);
975 if (wasUsed) { 1072 if (wasUsed) {
@@ -1012,32 +1109,60 @@ iBool postContextClick_Window(iWindow *d, const SDL_MouseButtonEvent *ev) {
1012} 1109}
1013 1110
1014void draw_Window(iWindow *d) { 1111void draw_Window(iWindow *d) {
1112 if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) {
1113 return;
1114 }
1115 iPaint p;
1116 init_Paint(&p);
1117 iRoot *root = d->roots[0];
1118 setCurrent_Root(root);
1119 unsetClip_Paint(&p); /* update clip to full window */
1120 const iColor back = get_Color(uiBackground_ColorId);
1121 SDL_SetRenderDrawColor(d->render, back.r, back.g, back.b, 255);
1122 SDL_RenderClear(d->render);
1123 d->frameTime = SDL_GetTicks();
1124 if (isExposed_Window(d)) {
1125 d->isInvalidated = iFalse;
1126 extern int drawCount_;
1127 drawRoot_Widget(root->widget);
1128#if !defined (NDEBUG)
1129 draw_Text(defaultBold_FontId, safeRect_Root(root).pos, red_ColorId, "%d", drawCount_);
1130 drawCount_ = 0;
1131#endif
1132 }
1133// drawRectThickness_Paint(&p, (iRect){ zero_I2(), sub_I2(d->size, one_I2()) }, gap_UI / 4, uiSeparator_ColorId);
1134 setCurrent_Root(NULL);
1135 SDL_RenderPresent(d->render);
1136}
1137
1138void draw_MainWindow(iMainWindow *d) {
1139 /* TODO: Try to make this a specialization of `draw_Window`? */
1140 iWindow *w = as_Window(d);
1015 if (d->isDrawFrozen) { 1141 if (d->isDrawFrozen) {
1016 return; 1142 return;
1017 } 1143 }
1018//#if defined (iPlatformMobile) 1144 setCurrent_Text(d->base.text);
1019 /* Check if root needs resizing. */ { 1145 /* Check if root needs resizing. */ {
1020 iInt2 renderSize; 1146 iInt2 renderSize;
1021 SDL_GetRendererOutputSize(d->render, &renderSize.x, &renderSize.y); 1147 SDL_GetRendererOutputSize(w->render, &renderSize.x, &renderSize.y);
1022 if (!isEqual_I2(renderSize, d->size)) { 1148 if (!isEqual_I2(renderSize, w->size)) {
1023 updateSize_Window_(d, iTrue); 1149 updateSize_MainWindow_(d, iTrue);
1024 processEvents_App(postedEventsOnly_AppEventMode); 1150 processEvents_App(postedEventsOnly_AppEventMode);
1025 } 1151 }
1026 } 1152 }
1027//#endif 1153 const int winFlags = SDL_GetWindowFlags(d->base.win);
1028 const int winFlags = SDL_GetWindowFlags(d->win);
1029 const iBool gotFocus = (winFlags & SDL_WINDOW_INPUT_FOCUS) != 0; 1154 const iBool gotFocus = (winFlags & SDL_WINDOW_INPUT_FOCUS) != 0;
1030 iPaint p; 1155 iPaint p;
1031 init_Paint(&p); 1156 init_Paint(&p);
1032 /* Clear the window. The clear color is visible as a border around the window 1157 /* Clear the window. The clear color is visible as a border around the window
1033 when the custom frame is being used. */ { 1158 when the custom frame is being used. */ {
1034 setCurrent_Root(d->roots[0]); 1159 setCurrent_Root(w->roots[0]);
1035#if defined (iPlatformAppleMobile) 1160#if defined (iPlatformMobile)
1036 iColor back = get_Color(uiBackground_ColorId); 1161 iColor back = get_Color(uiBackground_ColorId);
1037 if (deviceType_App() == phone_AppDeviceType) { 1162 if (deviceType_App() == phone_AppDeviceType) {
1038 /* Page background extends to safe area, so fill it completely. */ 1163 /* Page background extends to safe area, so fill it completely. */
1039 back = get_Color(tmBackground_ColorId); 1164 back = get_Color(tmBackground_ColorId);
1040 } 1165 }
1041#else 1166#else
1042 const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap && 1167 const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap &&
1043 ~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP 1168 ~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP
@@ -1045,19 +1170,20 @@ void draw_Window(iWindow *d) {
1045 : uiSeparator_ColorId); 1170 : uiSeparator_ColorId);
1046#endif 1171#endif
1047 unsetClip_Paint(&p); /* update clip to full window */ 1172 unsetClip_Paint(&p); /* update clip to full window */
1048 SDL_SetRenderDrawColor(d->render, back.r, back.g, back.b, 255); 1173 SDL_SetRenderDrawColor(w->render, back.r, back.g, back.b, 255);
1049 SDL_RenderClear(d->render); 1174 SDL_RenderClear(w->render);
1050 } 1175 }
1051 /* Draw widgets. */ 1176 /* Draw widgets. */
1052 d->frameTime = SDL_GetTicks(); 1177 w->frameTime = SDL_GetTicks();
1053 if (isExposed_Window(d)) { 1178 if (isExposed_Window(w)) {
1054 d->isInvalidated = iFalse; 1179 w->isInvalidated = iFalse;
1055 iForIndices(i, d->roots) { 1180 extern int drawCount_;
1056 iRoot *root = d->roots[i]; 1181 iForIndices(i, w->roots) {
1182 iRoot *root = w->roots[i];
1057 if (root) { 1183 if (root) {
1058 setCurrent_Root(root); 1184 setCurrent_Root(root);
1059 unsetClip_Paint(&p); /* update clip to current root */ 1185 unsetClip_Paint(&p); /* update clip to current root */
1060 draw_Widget(root->widget); 1186 drawRoot_Widget(root->widget);
1061#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 1187#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
1062 /* App icon. */ 1188 /* App icon. */
1063 const iWidget *appIcon = findChild_Widget(root->widget, "winbar.icon"); 1189 const iWidget *appIcon = findChild_Widget(root->widget, "winbar.icon");
@@ -1070,14 +1196,14 @@ void draw_Window(iWindow *d) {
1070 SDL_SetTextureColorMod(d->appIcon, iconColor.r, iconColor.g, iconColor.b); 1196 SDL_SetTextureColorMod(d->appIcon, iconColor.r, iconColor.g, iconColor.b);
1071 SDL_SetTextureAlphaMod(d->appIcon, gotFocus || !isLight ? 255 : 92); 1197 SDL_SetTextureAlphaMod(d->appIcon, gotFocus || !isLight ? 255 : 92);
1072 SDL_RenderCopy( 1198 SDL_RenderCopy(
1073 d->render, 1199 w->render,
1074 d->appIcon, 1200 d->appIcon,
1075 NULL, 1201 NULL,
1076 &(SDL_Rect){ left_Rect(rect) + gap_UI * 1.25f, mid.y - size / 2, size, size }); 1202 &(SDL_Rect){ left_Rect(rect) + gap_UI * 1.25f, mid.y - size / 2, size, size });
1077 } 1203 }
1078#endif 1204#endif
1079 /* Root separator and keyboard focus indicator. */ 1205 /* Root separator and keyboard focus indicator. */
1080 if (numRoots_Window(d) > 1){ 1206 if (numRoots_Window(w) > 1){
1081 const iRect bounds = bounds_Widget(root->widget); 1207 const iRect bounds = bounds_Widget(root->widget);
1082 if (i == 1) { 1208 if (i == 1) {
1083 fillRect_Paint(&p, (iRect){ 1209 fillRect_Paint(&p, (iRect){
@@ -1085,7 +1211,7 @@ void draw_Window(iWindow *d) {
1085 init_I2(gap_UI / 4, height_Rect(bounds)) 1211 init_I2(gap_UI / 4, height_Rect(bounds))
1086 }, uiSeparator_ColorId); 1212 }, uiSeparator_ColorId);
1087 } 1213 }
1088 if (root == d->keyRoot) { 1214 if (root == w->keyRoot) {
1089 const iBool isDark = isDark_ColorTheme(colorTheme_App()); 1215 const iBool isDark = isDark_ColorTheme(colorTheme_App());
1090 fillRect_Paint(&p, (iRect){ 1216 fillRect_Paint(&p, (iRect){
1091 topLeft_Rect(bounds), 1217 topLeft_Rect(bounds),
@@ -1097,6 +1223,10 @@ void draw_Window(iWindow *d) {
1097 } 1223 }
1098 } 1224 }
1099 setCurrent_Root(NULL); 1225 setCurrent_Root(NULL);
1226#if !defined (NDEBUG)
1227 draw_Text(defaultBold_FontId, safeRect_Root(w->roots[0]).pos, red_ColorId, "%d", drawCount_);
1228 drawCount_ = 0;
1229#endif
1100 } 1230 }
1101#if 0 1231#if 0
1102 /* Text cache debugging. */ { 1232 /* Text cache debugging. */ {
@@ -1106,21 +1236,21 @@ void draw_Window(iWindow *d) {
1106 SDL_RenderCopy(d->render, glyphCache_Text(), NULL, &rect); 1236 SDL_RenderCopy(d->render, glyphCache_Text(), NULL, &rect);
1107 } 1237 }
1108#endif 1238#endif
1109 SDL_RenderPresent(d->render); 1239 SDL_RenderPresent(w->render);
1110} 1240}
1111 1241
1112void resize_Window(iWindow *d, int w, int h) { 1242void resize_MainWindow(iMainWindow *d, int w, int h) {
1113 if (w > 0 && h > 0) { 1243 if (w > 0 && h > 0) {
1114 SDL_SetWindowSize(d->win, w, h); 1244 SDL_SetWindowSize(d->base.win, w, h);
1115 updateSize_Window_(d, iFalse); 1245 updateSize_MainWindow_(d, iFalse);
1116 } 1246 }
1117 else { 1247 else {
1118 updateSize_Window_(d, iTrue); /* notify always */ 1248 updateSize_MainWindow_(d, iTrue); /* notify always */
1119 } 1249 }
1120} 1250}
1121 1251
1122void setTitle_Window(iWindow *d, const iString *title) { 1252void setTitle_MainWindow(iMainWindow *d, const iString *title) {
1123 SDL_SetWindowTitle(d->win, cstr_String(title)); 1253 SDL_SetWindowTitle(d->base.win, cstr_String(title));
1124 iLabelWidget *bar = findChild_Widget(get_Root()->widget, "winbar.title"); 1254 iLabelWidget *bar = findChild_Widget(get_Root()->widget, "winbar.title");
1125 if (bar) { 1255 if (bar) {
1126 updateText_LabelWidget(bar, title); 1256 updateText_LabelWidget(bar, title);
@@ -1128,6 +1258,9 @@ void setTitle_Window(iWindow *d, const iString *title) {
1128} 1258}
1129 1259
1130void setUiScale_Window(iWindow *d, float uiScale) { 1260void setUiScale_Window(iWindow *d, float uiScale) {
1261 if (uiScale <= 0.0f) {
1262 uiScale = 1.0f;
1263 }
1131 uiScale = iClamp(uiScale, 0.5f, 4.0f); 1264 uiScale = iClamp(uiScale, 0.5f, 4.0f);
1132 if (d) { 1265 if (d) {
1133 if (iAbs(d->uiScale - uiScale) > 0.0001f) { 1266 if (iAbs(d->uiScale - uiScale) > 0.0001f) {
@@ -1140,7 +1273,7 @@ void setUiScale_Window(iWindow *d, float uiScale) {
1140 } 1273 }
1141} 1274}
1142 1275
1143void setFreezeDraw_Window(iWindow *d, iBool freezeDraw) { 1276void setFreezeDraw_MainWindow(iMainWindow *d, iBool freezeDraw) {
1144 d->isDrawFrozen = freezeDraw; 1277 d->isDrawFrozen = freezeDraw;
1145} 1278}
1146 1279
@@ -1191,63 +1324,80 @@ iWindow *get_Window(void) {
1191 return theWindow_; 1324 return theWindow_;
1192} 1325}
1193 1326
1327void setCurrent_Window(iAnyWindow *d) {
1328 theWindow_ = d;
1329 if (type_Window(d) == main_WindowType) {
1330 theMainWindow_ = d;
1331 }
1332 if (d) {
1333 setCurrent_Text(theWindow_->text);
1334 setCurrent_Root(theWindow_->keyRoot);
1335 }
1336 else {
1337 setCurrent_Text(NULL);
1338 setCurrent_Root(NULL);
1339 }
1340}
1341
1342iMainWindow *get_MainWindow(void) {
1343 return theMainWindow_;
1344}
1345
1194iBool isOpenGLRenderer_Window(void) { 1346iBool isOpenGLRenderer_Window(void) {
1195 return isOpenGLRenderer_; 1347 return isOpenGLRenderer_;
1196} 1348}
1197 1349
1198void setKeyboardHeight_Window(iWindow *d, int height) { 1350void setKeyboardHeight_MainWindow(iMainWindow *d, int height) {
1199 if (d->keyboardHeight != height) { 1351 if (d->keyboardHeight != height) {
1200 d->keyboardHeight = height; 1352 d->keyboardHeight = height;
1201 if (height == 0) {
1202 setFlags_Anim(&d->rootOffset, easeBoth_AnimFlag, iTrue);
1203 setValue_Anim(&d->rootOffset, 0, 250);
1204 }
1205 postCommandf_App("keyboard.changed arg:%d", height); 1353 postCommandf_App("keyboard.changed arg:%d", height);
1206 postRefresh_App(); 1354 postRefresh_App();
1207 } 1355 }
1208} 1356}
1209 1357
1210void checkPendingSplit_Window(iWindow *d) { 1358void checkPendingSplit_MainWindow(iMainWindow *d) {
1211 if (d->splitMode != d->pendingSplitMode) { 1359 if (d->splitMode != d->pendingSplitMode) {
1212 setSplitMode_Window(d, d->pendingSplitMode); 1360 setSplitMode_MainWindow(d, d->pendingSplitMode);
1213 } 1361 }
1214} 1362}
1215 1363
1216void swapRoots_Window(iWindow *d) { 1364void swapRoots_MainWindow(iMainWindow *d) {
1217 if (numRoots_Window(d) == 2) { 1365 iWindow *w = as_Window(d);
1218 iSwap(iRoot *, d->roots[0], d->roots[1]); 1366 if (numRoots_Window(w) == 2) {
1219 updateSize_Window_(d, iTrue); 1367 iSwap(iRoot *, w->roots[0], w->roots[1]);
1368 updateSize_MainWindow_(d, iTrue);
1220 } 1369 }
1221} 1370}
1222 1371
1223void setSplitMode_Window(iWindow *d, int splitFlags) { 1372void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) {
1224 const int splitMode = splitFlags & mode_WindowSplit; 1373 const int splitMode = splitFlags & mode_WindowSplit;
1225 if (deviceType_App() == phone_AppDeviceType) { 1374 if (deviceType_App() == phone_AppDeviceType) {
1226 /* There isn't enough room on the phone. */ 1375 /* There isn't enough room on the phone. */
1227 /* TODO: Maybe in landscape only? */ 1376 /* TODO: Maybe in landscape only? */
1228 return; 1377 return;
1229 } 1378 }
1379 iWindow *w = as_Window(d);
1230 iAssert(current_Root() == NULL); 1380 iAssert(current_Root() == NULL);
1231 if (d->splitMode != splitMode) { 1381 if (d->splitMode != splitMode) {
1232 int oldCount = numRoots_Window(d); 1382 int oldCount = numRoots_Window(w);
1233 setFreezeDraw_Window(d, iTrue); 1383 setFreezeDraw_MainWindow(d, iTrue);
1234 if (oldCount == 2 && splitMode == 0) { 1384 if (oldCount == 2 && splitMode == 0) {
1235 /* Keep references to the tabs of the second root. */ 1385 /* Keep references to the tabs of the second root. */
1236 const iDocumentWidget *curPage = document_Root(d->keyRoot); 1386 const iDocumentWidget *curPage = document_Root(w->keyRoot);
1237 if (!curPage) { 1387 if (!curPage) {
1238 /* All tabs closed on that side. */ 1388 /* All tabs closed on that side. */
1239 curPage = document_Root(otherRoot_Window(d, d->keyRoot)); 1389 curPage = document_Root(otherRoot_Window(w, w->keyRoot));
1240 } 1390 }
1241 iObjectList *tabs = listDocuments_App(d->roots[1]); 1391 iObjectList *tabs = listDocuments_App(w->roots[1]);
1242 iForEach(ObjectList, i, tabs) { 1392 iForEach(ObjectList, i, tabs) {
1243 setRoot_Widget(i.object, d->roots[0]); 1393 setRoot_Widget(i.object, w->roots[0]);
1244 } 1394 }
1245 setFocus_Widget(NULL); 1395 setFocus_Widget(NULL);
1246 delete_Root(d->roots[1]); 1396 delete_Root(w->roots[1]);
1247 d->roots[1] = NULL; 1397 w->roots[1] = NULL;
1248 d->keyRoot = d->roots[0]; 1398 w->keyRoot = w->roots[0];
1249 /* Move the deleted root's tabs to the first root. */ 1399 /* Move the deleted root's tabs to the first root. */
1250 setCurrent_Root(d->roots[0]); 1400 setCurrent_Root(w->roots[0]);
1251 iWidget *docTabs = findWidget_Root("doctabs"); 1401 iWidget *docTabs = findWidget_Root("doctabs");
1252 iForEach(ObjectList, j, tabs) { 1402 iForEach(ObjectList, j, tabs) {
1253 appendTabPage_Widget(docTabs, j.object, "", 0, 0); 1403 appendTabPage_Widget(docTabs, j.object, "", 0, 0);
@@ -1259,38 +1409,48 @@ void setSplitMode_Window(iWindow *d, int splitFlags) {
1259 } 1409 }
1260 else if (splitMode && oldCount == 1) { 1410 else if (splitMode && oldCount == 1) {
1261 /* Add a second root. */ 1411 /* Add a second root. */
1262 iDocumentWidget *moved = document_Root(d->roots[0]); 1412 iDocumentWidget *moved = document_Root(w->roots[0]);
1263 iAssert(d->roots[1] == NULL); 1413 iAssert(w->roots[1] == NULL);
1264 const iBool addToLeft = (prefs_App()->pinSplit == 2); 1414 const iBool addToLeft = (prefs_App()->pinSplit == 2);
1265 size_t newRootIndex = 1; 1415 size_t newRootIndex = 1;
1266 if (addToLeft) { 1416 if (addToLeft) {
1267 iSwap(iRoot *, d->roots[0], d->roots[1]); 1417 iSwap(iRoot *, w->roots[0], w->roots[1]);
1268 newRootIndex = 0; 1418 newRootIndex = 0;
1269 } 1419 }
1270 d->roots[newRootIndex] = new_Root(); 1420 w->roots[newRootIndex] = new_Root();
1271 d->keyRoot = d->roots[newRootIndex]; 1421 w->keyRoot = w->roots[newRootIndex];
1272 setCurrent_Root(d->roots[newRootIndex]); 1422 w->keyRoot->window = w;
1273 createUserInterface_Root(d->roots[newRootIndex]); 1423 setCurrent_Root(w->roots[newRootIndex]);
1424 createUserInterface_Root(w->roots[newRootIndex]);
1425 /* Bookmark folder state will match the old root's state. */ {
1426 for (int sb = 0; sb < 2; sb++) {
1427 const char *sbId = (sb == 0 ? "sidebar" : "sidebar2");
1428 setClosedFolders_SidebarWidget(
1429 findChild_Widget(w->roots[newRootIndex]->widget, sbId),
1430 closedFolders_SidebarWidget(
1431 findChild_Widget(w->roots[newRootIndex ^ 1]->widget, sbId)));
1432 }
1433 }
1274 if (!isEmpty_String(d->pendingSplitUrl)) { 1434 if (!isEmpty_String(d->pendingSplitUrl)) {
1275 postCommandf_Root(d->roots[newRootIndex], "open url:%s", 1435 postCommandf_Root(w->roots[newRootIndex], "open url:%s",
1276 cstr_String(d->pendingSplitUrl)); 1436 cstr_String(d->pendingSplitUrl));
1277 clear_String(d->pendingSplitUrl); 1437 clear_String(d->pendingSplitUrl);
1278 } 1438 }
1279 else if (~splitFlags & noEvents_WindowSplit) { 1439 else if (~splitFlags & noEvents_WindowSplit) {
1280 iWidget *docTabs0 = findChild_Widget(d->roots[newRootIndex ^ 1]->widget, "doctabs"); 1440 iWidget *docTabs0 = findChild_Widget(w->roots[newRootIndex ^ 1]->widget, "doctabs");
1281 iWidget *docTabs1 = findChild_Widget(d->roots[newRootIndex]->widget, "doctabs"); 1441 iWidget *docTabs1 = findChild_Widget(w->roots[newRootIndex]->widget, "doctabs");
1282 /* If the old root has multiple tabs, move the current one to the new split. */ 1442 /* If the old root has multiple tabs, move the current one to the new split. */
1283 if (tabCount_Widget(docTabs0) >= 2) { 1443 if (tabCount_Widget(docTabs0) >= 2) {
1284 int movedIndex = tabPageIndex_Widget(docTabs0, moved); 1444 int movedIndex = tabPageIndex_Widget(docTabs0, moved);
1285 removeTabPage_Widget(docTabs0, movedIndex); 1445 removeTabPage_Widget(docTabs0, movedIndex);
1286 showTabPage_Widget(docTabs0, tabPage_Widget(docTabs0, iMax(movedIndex - 1, 0))); 1446 showTabPage_Widget(docTabs0, tabPage_Widget(docTabs0, iMax(movedIndex - 1, 0)));
1287 iRelease(removeTabPage_Widget(docTabs1, 0)); /* delete the default tab */ 1447 iRelease(removeTabPage_Widget(docTabs1, 0)); /* delete the default tab */
1288 setRoot_Widget(as_Widget(moved), d->roots[newRootIndex]); 1448 setRoot_Widget(as_Widget(moved), w->roots[newRootIndex]);
1289 prependTabPage_Widget(docTabs1, iClob(moved), "", 0, 0); 1449 prependTabPage_Widget(docTabs1, iClob(moved), "", 0, 0);
1290 postCommandf_App("tabs.switch page:%p", moved); 1450 postCommandf_App("tabs.switch page:%p", moved);
1291 } 1451 }
1292 else { 1452 else {
1293 postCommand_Root(d->roots[newRootIndex], "navigate.home"); 1453 postCommand_Root(w->roots[newRootIndex], "navigate.home");
1294 } 1454 }
1295 } 1455 }
1296 setCurrent_Root(NULL); 1456 setCurrent_Root(NULL);
@@ -1319,26 +1479,26 @@ void setSplitMode_Window(iWindow *d, int splitFlags) {
1319 } 1479 }
1320#endif 1480#endif
1321 if (~splitFlags & noEvents_WindowSplit) { 1481 if (~splitFlags & noEvents_WindowSplit) {
1322 updateSize_Window_(d, iTrue); 1482 updateSize_MainWindow_(d, iTrue);
1323 postCommand_App("window.unfreeze"); 1483 postCommand_App("window.unfreeze");
1324 } 1484 }
1325 } 1485 }
1326} 1486}
1327 1487
1328void setSnap_Window(iWindow *d, int snapMode) { 1488void setSnap_MainWindow(iMainWindow *d, int snapMode) {
1329 if (!prefs_App()->customFrame) { 1489 if (!prefs_App()->customFrame) {
1330 if (snapMode == maximized_WindowSnap) { 1490 if (snapMode == maximized_WindowSnap) {
1331 SDL_MaximizeWindow(d->win); 1491 SDL_MaximizeWindow(d->base.win);
1332 } 1492 }
1333 else if (snapMode == fullscreen_WindowSnap) { 1493 else if (snapMode == fullscreen_WindowSnap) {
1334 SDL_SetWindowFullscreen(d->win, SDL_WINDOW_FULLSCREEN_DESKTOP); 1494 SDL_SetWindowFullscreen(d->base.win, SDL_WINDOW_FULLSCREEN_DESKTOP);
1335 } 1495 }
1336 else { 1496 else {
1337 if (snap_Window(d) == fullscreen_WindowSnap) { 1497 if (snap_MainWindow(d) == fullscreen_WindowSnap) {
1338 SDL_SetWindowFullscreen(d->win, 0); 1498 SDL_SetWindowFullscreen(d->base.win, 0);
1339 } 1499 }
1340 else { 1500 else {
1341 SDL_RestoreWindow(d->win); 1501 SDL_RestoreWindow(d->base.win);
1342 } 1502 }
1343 } 1503 }
1344 return; 1504 return;
@@ -1350,9 +1510,9 @@ void setSnap_Window(iWindow *d, int snapMode) {
1350 const int snapDist = gap_UI * 4; 1510 const int snapDist = gap_UI * 4;
1351 iRect newRect = zero_Rect(); 1511 iRect newRect = zero_Rect();
1352 SDL_Rect usable; 1512 SDL_Rect usable;
1353 SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->win), &usable); 1513 SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->base.win), &usable);
1354 if (d->place.snap == fullscreen_WindowSnap) { 1514 if (d->place.snap == fullscreen_WindowSnap) {
1355 SDL_SetWindowFullscreen(d->win, 0); 1515 SDL_SetWindowFullscreen(d->base.win, 0);
1356 } 1516 }
1357 d->place.snap = snapMode & ~redo_WindowSnap; 1517 d->place.snap = snapMode & ~redo_WindowSnap;
1358 switch (snapMode & mask_WindowSnap) { 1518 switch (snapMode & mask_WindowSnap) {
@@ -1373,8 +1533,8 @@ void setSnap_Window(iWindow *d, int snapMode) {
1373 case yMaximized_WindowSnap: 1533 case yMaximized_WindowSnap:
1374 newRect.pos.y = 0; 1534 newRect.pos.y = 0;
1375 newRect.size.y = usable.h; 1535 newRect.size.y = usable.h;
1376 SDL_GetWindowSize(d->win, &newRect.size.x, NULL); 1536 SDL_GetWindowSize(d->base.win, &newRect.size.x, NULL);
1377 SDL_GetWindowPosition(d->win, &newRect.pos.x, NULL); 1537 SDL_GetWindowPosition(d->base.win, &newRect.pos.x, NULL);
1378 /* Snap the window to left/right edges, if close by. */ 1538 /* Snap the window to left/right edges, if close by. */
1379 if (iAbs(right_Rect(newRect) - (usable.x + usable.w)) < snapDist) { 1539 if (iAbs(right_Rect(newRect) - (usable.x + usable.w)) < snapDist) {
1380 newRect.pos.x = usable.x + usable.w - width_Rect(newRect); 1540 newRect.pos.x = usable.x + usable.w - width_Rect(newRect);
@@ -1384,7 +1544,7 @@ void setSnap_Window(iWindow *d, int snapMode) {
1384 } 1544 }
1385 break; 1545 break;
1386 case fullscreen_WindowSnap: 1546 case fullscreen_WindowSnap:
1387 SDL_SetWindowFullscreen(d->win, SDL_WINDOW_FULLSCREEN_DESKTOP); 1547 SDL_SetWindowFullscreen(d->base.win, SDL_WINDOW_FULLSCREEN_DESKTOP);
1388 break; 1548 break;
1389 } 1549 }
1390 if (snapMode & (topBit_WindowSnap | bottomBit_WindowSnap)) { 1550 if (snapMode & (topBit_WindowSnap | bottomBit_WindowSnap)) {
@@ -1416,9 +1576,9 @@ void setSnap_Window(iWindow *d, int snapMode) {
1416#endif /* defined (LAGRANGE_ENABLE_CUSTOM_FRAME) */ 1576#endif /* defined (LAGRANGE_ENABLE_CUSTOM_FRAME) */
1417} 1577}
1418 1578
1419int snap_Window(const iWindow *d) { 1579int snap_MainWindow(const iMainWindow *d) {
1420 if (!prefs_App()->customFrame) { 1580 if (!prefs_App()->customFrame) {
1421 const int flags = SDL_GetWindowFlags(d->win); 1581 const int flags = SDL_GetWindowFlags(d->base.win);
1422 if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) { 1582 if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) {
1423 return fullscreen_WindowSnap; 1583 return fullscreen_WindowSnap;
1424 } 1584 }
@@ -1429,3 +1589,27 @@ int snap_Window(const iWindow *d) {
1429 } 1589 }
1430 return d->place.snap; 1590 return d->place.snap;
1431} 1591}
1592
1593/*----------------------------------------------------------------------------------------------*/
1594
1595iWindow *newPopup_Window(iInt2 screenPos, iWidget *rootWidget) {
1596 iWindow *win =
1597 new_Window(popup_WindowType,
1598 (iRect){ screenPos, divf_I2(rootWidget->rect.size, get_Window()->pixelRatio) },
1599 SDL_WINDOW_ALWAYS_ON_TOP |
1600#if !defined (iPlatformAppleDesktop)
1601 SDL_WINDOW_BORDERLESS |
1602#endif
1603 SDL_WINDOW_POPUP_MENU |
1604 SDL_WINDOW_SKIP_TASKBAR);
1605#if defined (iPlatformAppleDesktop)
1606 hideTitleBar_MacOS(win); /* make it a borderless window */
1607#endif
1608 iRoot *root = new_Root();
1609 win->roots[0] = root;
1610 win->keyRoot = root;
1611 root->widget = rootWidget;
1612 root->window = win;
1613 setRoot_Widget(rootWidget, root);
1614 return win;
1615}