summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/documentwidget.c2
-rw-r--r--src/ui/inputwidget.c2
-rw-r--r--src/ui/root.c10
-rw-r--r--src/ui/root.h2
-rw-r--r--src/ui/text.c93
-rw-r--r--src/ui/text.h17
-rw-r--r--src/ui/text_simple.c16
-rw-r--r--src/ui/util.c42
-rw-r--r--src/ui/widget.c38
-rw-r--r--src/ui/widget.h4
-rw-r--r--src/ui/window.c257
-rw-r--r--src/ui/window.h34
12 files changed, 361 insertions, 156 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index ed9e41d6..6f9824de 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -750,7 +750,7 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) {
750 if (document_App() != d) { 750 if (document_App() != d) {
751 return 0; 751 return 0;
752 } 752 }
753 if (get_Window()->isDrawFrozen) { 753 if (as_MainWindow(window_Widget(d))->isDrawFrozen) {
754 return 0; 754 return 0;
755 } 755 }
756 static const uint32_t invalidInterval_ = ~0u; 756 static const uint32_t invalidInterval_ = ~0u;
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index a561d5bd..f02bf408 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -2352,7 +2352,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
2352 } 2352 }
2353 /* Draw the insertion point. */ 2353 /* Draw the insertion point. */
2354 if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) && 2354 if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) &&
2355 isEmpty_Range(&d->mark)) { 2355 (deviceType_App() == desktop_AppDeviceType || isEmpty_Range(&d->mark))) {
2356 iInt2 curSize; 2356 iInt2 curSize;
2357 iRangecc cursorChar = iNullRange; 2357 iRangecc cursorChar = iNullRange;
2358 int visWrapsAbove = 0; 2358 int visWrapsAbove = 0;
diff --git a/src/ui/root.c b/src/ui/root.c
index 52a08eca..9e290b05 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -298,16 +298,6 @@ void destroyPending_Root(iRoot *d) {
298 setCurrent_Root(oldRoot); 298 setCurrent_Root(oldRoot);
299} 299}
300 300
301void postArrange_Root(iRoot *d) {
302 if (!d->pendingArrange) {
303 d->pendingArrange = iTrue;
304 SDL_Event ev = { .type = SDL_USEREVENT };
305 ev.user.code = arrange_UserEventCode;
306 ev.user.data2 = d;
307 SDL_PushEvent(&ev);
308 }
309}
310
311iPtrArray *onTop_Root(iRoot *d) { 301iPtrArray *onTop_Root(iRoot *d) {
312 if (!d->onTop) { 302 if (!d->onTop) {
313 d->onTop = new_PtrArray(); 303 d->onTop = new_PtrArray();
diff --git a/src/ui/root.h b/src/ui/root.h
index 740e97c9..04dd5e16 100644
--- a/src/ui/root.h
+++ b/src/ui/root.h
@@ -9,6 +9,7 @@ iDeclareType(Root)
9 9
10struct Impl_Root { 10struct Impl_Root {
11 iWidget * widget; 11 iWidget * widget;
12 iWindow * window;
12 iPtrArray *onTop; /* order is important; last one is topmost */ 13 iPtrArray *onTop; /* order is important; last one is topmost */
13 iPtrSet * pendingDestruction; 14 iPtrSet * pendingDestruction;
14 iBool pendingArrange; 15 iBool pendingArrange;
@@ -29,7 +30,6 @@ iAnyObject *findWidget_Root (const char *id); /* under curre
29 30
30iPtrArray * onTop_Root (iRoot *); 31iPtrArray * onTop_Root (iRoot *);
31void destroyPending_Root (iRoot *); 32void destroyPending_Root (iRoot *);
32void postArrange_Root (iRoot *);
33 33
34void updateMetrics_Root (iRoot *); 34void updateMetrics_Root (iRoot *);
35void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */ 35void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */
diff --git a/src/ui/text.c b/src/ui/text.c
index f7fff4bc..bf71b0e9 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -290,7 +290,9 @@ struct Impl_Text {
290 iRegExp * ansiEscape; 290 iRegExp * ansiEscape;
291}; 291};
292 292
293static iText text_; 293iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render)
294
295static iText *activeText_;
294static iBlock *userFont_; 296static iBlock *userFont_;
295 297
296static void initFonts_Text_(iText *d) { 298static void initFonts_Text_(iText *d) {
@@ -501,8 +503,7 @@ void loadUserFonts_Text(void) {
501 } 503 }
502} 504}
503 505
504void init_Text(SDL_Renderer *render) { 506void init_Text(iText *d, SDL_Renderer *render) {
505 iText *d = &text_;
506 loadUserFonts_Text(); 507 loadUserFonts_Text();
507 d->contentFont = nunito_TextFont; 508 d->contentFont = nunito_TextFont;
508 d->headingFont = nunito_TextFont; 509 d->headingFont = nunito_TextFont;
@@ -521,8 +522,7 @@ void init_Text(SDL_Renderer *render) {
521 initFonts_Text_(d); 522 initFonts_Text_(d);
522} 523}
523 524
524void deinit_Text(void) { 525void deinit_Text(iText *d) {
525 iText *d = &text_;
526 SDL_FreePalette(d->grayscale); 526 SDL_FreePalette(d->grayscale);
527 deinitFonts_Text_(d); 527 deinitFonts_Text_(d);
528 deinitCache_Text_(d); 528 deinitCache_Text_(d);
@@ -530,30 +530,34 @@ void deinit_Text(void) {
530 iRelease(d->ansiEscape); 530 iRelease(d->ansiEscape);
531} 531}
532 532
533void setCurrent_Text(iText *d) {
534 activeText_ = d;
535}
536
533void setOpacity_Text(float opacity) { 537void setOpacity_Text(float opacity) {
534 SDL_SetTextureAlphaMod(text_.cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f); 538 SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f);
535} 539}
536 540
537void setContentFont_Text(enum iTextFont font) { 541void setContentFont_Text(iText *d, enum iTextFont font) {
538 if (text_.contentFont != font) { 542 if (d->contentFont != font) {
539 text_.contentFont = font; 543 d->contentFont = font;
540 resetFonts_Text(); 544 resetFonts_Text(d);
541 } 545 }
542} 546}
543 547
544void setHeadingFont_Text(enum iTextFont font) { 548void setHeadingFont_Text(iText *d, enum iTextFont font) {
545 if (text_.headingFont != font) { 549 if (d->headingFont != font) {
546 text_.headingFont = font; 550 d->headingFont = font;
547 resetFonts_Text(); 551 resetFonts_Text(d);
548 } 552 }
549} 553}
550 554
551void setContentFontSize_Text(float fontSizeFactor) { 555void setContentFontSize_Text(iText *d, float fontSizeFactor) {
552 fontSizeFactor *= contentScale_Text_; 556 fontSizeFactor *= contentScale_Text_;
553 iAssert(fontSizeFactor > 0); 557 iAssert(fontSizeFactor > 0);
554 if (iAbs(text_.contentFontSize - fontSizeFactor) > 0.001f) { 558 if (iAbs(d->contentFontSize - fontSizeFactor) > 0.001f) {
555 text_.contentFontSize = fontSizeFactor; 559 d->contentFontSize = fontSizeFactor;
556 resetFonts_Text(); 560 resetFonts_Text(d);
557 } 561 }
558} 562}
559 563
@@ -565,8 +569,7 @@ static void resetCache_Text_(iText *d) {
565 initCache_Text_(d); 569 initCache_Text_(d);
566} 570}
567 571
568void resetFonts_Text(void) { 572void resetFonts_Text(iText *d) {
569 iText *d = &text_;
570 deinitFonts_Text_(d); 573 deinitFonts_Text_(d);
571 deinitCache_Text_(d); 574 deinitCache_Text_(d);
572 initCache_Text_(d); 575 initCache_Text_(d);
@@ -574,7 +577,7 @@ void resetFonts_Text(void) {
574} 577}
575 578
576iLocalDef iFont *font_Text_(enum iFontId id) { 579iLocalDef iFont *font_Text_(enum iFontId id) {
577 return &text_.fonts[id & mask_FontId]; 580 return &activeText_->fonts[id & mask_FontId];
578} 581}
579 582
580static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) { 583static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) {
@@ -584,7 +587,7 @@ static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, fl
584 SDL_Surface *surface8 = 587 SDL_Surface *surface8 =
585 SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); 588 SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8);
586 SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE); 589 SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE);
587 SDL_SetSurfacePalette(surface8, text_.grayscale); 590 SDL_SetSurfacePalette(surface8, activeText_->grayscale);
588#if LAGRANGE_RASTER_DEPTH != 8 591#if LAGRANGE_RASTER_DEPTH != 8
589 /* Convert to the cache format. */ 592 /* Convert to the cache format. */
590 SDL_Surface *surf = SDL_ConvertSurfaceFormat(surface8, LAGRANGE_RASTER_FORMAT, 0); 593 SDL_Surface *surf = SDL_ConvertSurfaceFormat(surface8, LAGRANGE_RASTER_FORMAT, 0);
@@ -631,7 +634,7 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) {
631 &d->font, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1); 634 &d->font, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1);
632 glRect->size = init_I2(x1 - x0, y1 - y0); 635 glRect->size = init_I2(x1 - x0, y1 - y0);
633 /* Determine placement in the glyph cache texture, advancing in rows. */ 636 /* Determine placement in the glyph cache texture, advancing in rows. */
634 glRect->pos = assignCachePos_Text_(&text_, glRect->size); 637 glRect->pos = assignCachePos_Text_(activeText_, glRect->size);
635 glyph->d[hoff] = init_I2(x0, y0); 638 glyph->d[hoff] = init_I2(x0, y0);
636 glyph->d[hoff].y += d->vertOffset; 639 glyph->d[hoff].y += d->vertOffset;
637 if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */ 640 if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */
@@ -737,11 +740,11 @@ static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) {
737 } 740 }
738 else { 741 else {
739 /* If the cache is running out of space, clear it and we'll recache what's needed currently. */ 742 /* If the cache is running out of space, clear it and we'll recache what's needed currently. */
740 if (text_.cacheBottom > text_.cacheSize.y - maxGlyphHeight_Text_(&text_)) { 743 if (activeText_->cacheBottom > activeText_->cacheSize.y - maxGlyphHeight_Text_(activeText_)) {
741#if !defined (NDEBUG) 744#if !defined (NDEBUG)
742 printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout); 745 printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout);
743#endif 746#endif
744 resetCache_Text_(&text_); 747 resetCache_Text_(activeText_);
745 } 748 }
746 glyph = new_Glyph(glyphIndex); 749 glyph = new_Glyph(glyphIndex);
747 glyph->font = d; 750 glyph->font = d;
@@ -858,7 +861,7 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i
858} 861}
859 862
860static enum iFontId fontId_Text_(const iFont *font) { 863static enum iFontId fontId_Text_(const iFont *font) {
861 return (enum iFontId) (font - text_.fonts); 864 return (enum iFontId) (font - activeText_->fonts);
862} 865}
863 866
864static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { 867static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) {
@@ -949,7 +952,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
949 /* Do a regexp match in the source text. */ 952 /* Do a regexp match in the source text. */
950 iRegExpMatch m; 953 iRegExpMatch m;
951 init_RegExpMatch(&m); 954 init_RegExpMatch(&m);
952 if (match_RegExp(text_.ansiEscape, srcPos, d->source.end - srcPos, &m)) { 955 if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) {
953 finishRun_AttributedText_(d, &run, pos - 1); 956 finishRun_AttributedText_(d, &run, pos - 1);
954 run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), 957 run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1),
955 tmParagraph_ColorId); 958 tmParagraph_ColorId);
@@ -1082,9 +1085,9 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {
1082 while (index < size_Array(glyphIndices)) { 1085 while (index < size_Array(glyphIndices)) {
1083 for (; index < size_Array(glyphIndices); index++) { 1086 for (; index < size_Array(glyphIndices); index++) {
1084 const uint32_t glyphIndex = constValue_Array(glyphIndices, index, uint32_t); 1087 const uint32_t glyphIndex = constValue_Array(glyphIndices, index, uint32_t);
1085 const int lastCacheBottom = text_.cacheBottom; 1088 const int lastCacheBottom = activeText_->cacheBottom;
1086 iGlyph *glyph = glyphByIndex_Font_(d, glyphIndex); 1089 iGlyph *glyph = glyphByIndex_Font_(d, glyphIndex);
1087 if (text_.cacheBottom < lastCacheBottom) { 1090 if (activeText_->cacheBottom < lastCacheBottom) {
1088 /* The cache was reset due to running out of space. We need to restart from 1091 /* The cache was reset due to running out of space. We need to restart from
1089 the beginning! */ 1092 the beginning! */
1090 bufX = 0; 1093 bufX = 0;
@@ -1103,7 +1106,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {
1103 LAGRANGE_RASTER_DEPTH, 1106 LAGRANGE_RASTER_DEPTH,
1104 LAGRANGE_RASTER_FORMAT); 1107 LAGRANGE_RASTER_FORMAT);
1105 SDL_SetSurfaceBlendMode(buf, SDL_BLENDMODE_NONE); 1108 SDL_SetSurfaceBlendMode(buf, SDL_BLENDMODE_NONE);
1106 SDL_SetSurfacePalette(buf, text_.grayscale); 1109 SDL_SetSurfacePalette(buf, activeText_->grayscale);
1107 } 1110 }
1108 SDL_Surface *surfaces[2] = { 1111 SDL_Surface *surfaces[2] = {
1109 !isRasterized_Glyph_(glyph, 0) ? 1112 !isRasterized_Glyph_(glyph, 0) ?
@@ -1147,19 +1150,19 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {
1147 } 1150 }
1148 /* Finished or the buffer is full, copy the glyphs to the cache texture. */ 1151 /* Finished or the buffer is full, copy the glyphs to the cache texture. */
1149 if (!isEmpty_Array(rasters)) { 1152 if (!isEmpty_Array(rasters)) {
1150 SDL_Texture *bufTex = SDL_CreateTextureFromSurface(text_.render, buf); 1153 SDL_Texture *bufTex = SDL_CreateTextureFromSurface(activeText_->render, buf);
1151 SDL_SetTextureBlendMode(bufTex, SDL_BLENDMODE_NONE); 1154 SDL_SetTextureBlendMode(bufTex, SDL_BLENDMODE_NONE);
1152 if (!isTargetChanged) { 1155 if (!isTargetChanged) {
1153 isTargetChanged = iTrue; 1156 isTargetChanged = iTrue;
1154 oldTarget = SDL_GetRenderTarget(text_.render); 1157 oldTarget = SDL_GetRenderTarget(activeText_->render);
1155 SDL_SetRenderTarget(text_.render, text_.cache); 1158 SDL_SetRenderTarget(activeText_->render, activeText_->cache);
1156 } 1159 }
1157// printf("copying %zu rasters from %p\n", size_Array(rasters), bufTex); fflush(stdout); 1160// printf("copying %zu rasters from %p\n", size_Array(rasters), bufTex); fflush(stdout);
1158 iConstForEach(Array, i, rasters) { 1161 iConstForEach(Array, i, rasters) {
1159 const iRasterGlyph *rg = i.value; 1162 const iRasterGlyph *rg = i.value;
1160// iAssert(isEqual_I2(rg->rect.size, rg->glyph->rect[rg->hoff].size)); 1163// iAssert(isEqual_I2(rg->rect.size, rg->glyph->rect[rg->hoff].size));
1161 const iRect *glRect = &rg->glyph->rect[rg->hoff]; 1164 const iRect *glRect = &rg->glyph->rect[rg->hoff];
1162 SDL_RenderCopy(text_.render, 1165 SDL_RenderCopy(activeText_->render,
1163 bufTex, 1166 bufTex,
1164 (const SDL_Rect *) &rg->rect, 1167 (const SDL_Rect *) &rg->rect,
1165 (const SDL_Rect *) glRect); 1168 (const SDL_Rect *) glRect);
@@ -1179,7 +1182,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {
1179 SDL_FreeSurface(buf); 1182 SDL_FreeSurface(buf);
1180 } 1183 }
1181 if (isTargetChanged) { 1184 if (isTargetChanged) {
1182 SDL_SetRenderTarget(text_.render, oldTarget); 1185 SDL_SetRenderTarget(activeText_->render, oldTarget);
1183 } 1186 }
1184} 1187}
1185 1188
@@ -1706,9 +1709,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1706 } 1709 }
1707 if (~mode & permanentColorFlag_RunMode) { 1710 if (~mode & permanentColorFlag_RunMode) {
1708 const iColor clr = run->fgColor; 1711 const iColor clr = run->fgColor;
1709 SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); 1712 SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);
1710 if (args->mode & fillBackground_RunMode) { 1713 if (args->mode & fillBackground_RunMode) {
1711 SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0); 1714 SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);
1712 } 1715 }
1713 } 1716 }
1714 SDL_Rect src; 1717 SDL_Rect src;
@@ -1719,9 +1722,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1719 /* Alpha blending looks much better if the RGB components don't change in 1722 /* Alpha blending looks much better if the RGB components don't change in
1720 the partially transparent pixels. */ 1723 the partially transparent pixels. */
1721 /* TODO: Backgrounds of all glyphs should be cleared before drawing anything else. */ 1724 /* TODO: Backgrounds of all glyphs should be cleared before drawing anything else. */
1722 SDL_RenderFillRect(text_.render, &dst); 1725 SDL_RenderFillRect(activeText_->render, &dst);
1723 } 1726 }
1724 SDL_RenderCopy(text_.render, text_.cache, &src, &dst); 1727 SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst);
1725#if 0 1728#if 0
1726 /* Show spaces and direction. */ 1729 /* Show spaces and direction. */
1727 if (logicalText[logPos] == 0x20) { 1730 if (logicalText[logPos] == 0x20) {
@@ -1863,7 +1866,7 @@ iTextMetrics measureN_Text(int fontId, const char *text, size_t n) {
1863} 1866}
1864 1867
1865static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) { 1868static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) {
1866 iText * d = &text_; 1869 iText * d = activeText_;
1867 iFont * font = font_Text_(fontId); 1870 iFont * font = font_Text_(fontId);
1868 const iColor clr = get_Color(color & mask_ColorId); 1871 const iColor clr = get_Color(color & mask_ColorId);
1869 SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b); 1872 SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b);
@@ -2057,7 +2060,7 @@ iTextMetrics draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) {
2057} 2060}
2058 2061
2059SDL_Texture *glyphCache_Text(void) { 2062SDL_Texture *glyphCache_Text(void) {
2060 return text_.cache; 2063 return activeText_->cache;
2061} 2064}
2062 2065
2063static void freeBitmap_(void *ptr) { 2066static void freeBitmap_(void *ptr) {
@@ -2170,7 +2173,7 @@ iString *renderBlockChars_Text(const iBlock *fontData, int height, enum iTextBlo
2170iDefineTypeConstructionArgs(TextBuf, (iWrapText *wrapText, int font, int color), wrapText, font, color) 2173iDefineTypeConstructionArgs(TextBuf, (iWrapText *wrapText, int font, int color), wrapText, font, color)
2171 2174
2172void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) { 2175void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) {
2173 SDL_Renderer *render = text_.render; 2176 SDL_Renderer *render = activeText_->render;
2174 d->size = measure_WrapText(wrapText, font).bounds.size; 2177 d->size = measure_WrapText(wrapText, font).bounds.size;
2175 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); 2178 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
2176 if (d->size.x * d->size.y) { 2179 if (d->size.x * d->size.y) {
@@ -2191,9 +2194,9 @@ void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) {
2191 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); 2194 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE);
2192 SDL_SetRenderDrawColor(render, 255, 255, 255, 0); 2195 SDL_SetRenderDrawColor(render, 255, 255, 255, 0);
2193 SDL_RenderClear(render); 2196 SDL_RenderClear(render);
2194 SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ 2197 SDL_SetTextureBlendMode(activeText_->cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */
2195 draw_WrapText(wrapText, font, zero_I2(), color | fillBackground_ColorId); 2198 draw_WrapText(wrapText, font, zero_I2(), color | fillBackground_ColorId);
2196 SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND); 2199 SDL_SetTextureBlendMode(activeText_->cache, SDL_BLENDMODE_BLEND);
2197 SDL_SetRenderTarget(render, oldTarget); 2200 SDL_SetRenderTarget(render, oldTarget);
2198 origin_Paint = oldOrigin; 2201 origin_Paint = oldOrigin;
2199 SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); 2202 SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND);
@@ -2212,7 +2215,7 @@ void draw_TextBuf(const iTextBuf *d, iInt2 pos, int color) {
2212 addv_I2(&pos, origin_Paint); 2215 addv_I2(&pos, origin_Paint);
2213 const iColor clr = get_Color(color); 2216 const iColor clr = get_Color(color);
2214 SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b); 2217 SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b);
2215 SDL_RenderCopy(text_.render, 2218 SDL_RenderCopy(activeText_->render,
2216 d->texture, 2219 d->texture,
2217 &(SDL_Rect){ 0, 0, d->size.x, d->size.y }, 2220 &(SDL_Rect){ 0, 0, d->size.x, d->size.y },
2218 &(SDL_Rect){ pos.x, pos.y, d->size.x, d->size.y }); 2221 &(SDL_Rect){ pos.x, pos.y, d->size.x, d->size.y });
diff --git a/src/ui/text.h b/src/ui/text.h
index ac6cc1c1..1da43818 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -139,15 +139,20 @@ enum iTextFont {
139 139
140extern int gap_Text; /* affected by content font size */ 140extern int gap_Text; /* affected by content font size */
141 141
142void init_Text (SDL_Renderer *); 142iDeclareType(Text)
143void deinit_Text (void); 143iDeclareTypeConstructionArgs(Text, SDL_Renderer *)
144
145void init_Text (iText *, SDL_Renderer *);
146void deinit_Text (iText *);
147
148void setCurrent_Text (iText *);
144 149
145void loadUserFonts_Text (void); /* based on Prefs */ 150void loadUserFonts_Text (void); /* based on Prefs */
146 151
147void setContentFont_Text (enum iTextFont font); 152void setContentFont_Text (iText *, enum iTextFont font);
148void setHeadingFont_Text (enum iTextFont font); 153void setHeadingFont_Text (iText *, enum iTextFont font);
149void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */ 154void setContentFontSize_Text (iText *, float fontSizeFactor); /* affects all except `default*` fonts */
150void resetFonts_Text (void); 155void resetFonts_Text (iText *);
151 156
152int lineHeight_Text (int fontId); 157int lineHeight_Text (int fontId);
153iRect visualBounds_Text (int fontId, iRangecc text); 158iRect visualBounds_Text (int fontId, iRangecc text);
diff --git a/src/ui/text_simple.c b/src/ui/text_simple.c
index bf33b4be..8b1de64a 100644
--- a/src/ui/text_simple.c
+++ b/src/ui/text_simple.c
@@ -92,7 +92,7 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
92 } 92 }
93 if (args->mode & fillBackground_RunMode) { 93 if (args->mode & fillBackground_RunMode) {
94 const iColor initial = get_Color(args->color); 94 const iColor initial = get_Color(args->color);
95 SDL_SetRenderDrawColor(text_.render, initial.r, initial.g, initial.b, 0); 95 SDL_SetRenderDrawColor(activeText_->render, initial.r, initial.g, initial.b, 0);
96 } 96 }
97 /* Text rendering is not very straightforward! Let's dive in... */ 97 /* Text rendering is not very straightforward! Let's dive in... */
98 iChar prevCh = 0; 98 iChar prevCh = 0;
@@ -114,14 +114,14 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
114 chPos++; 114 chPos++;
115 iRegExpMatch m; 115 iRegExpMatch m;
116 init_RegExpMatch(&m); 116 init_RegExpMatch(&m);
117 if (match_RegExp(text_.ansiEscape, chPos, args->text.end - chPos, &m)) { 117 if (match_RegExp(activeText_->ansiEscape, chPos, args->text.end - chPos, &m)) {
118 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { 118 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {
119 /* Change the color. */ 119 /* Change the color. */
120 const iColor clr = 120 const iColor clr =
121 ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), tmParagraph_ColorId); 121 ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), tmParagraph_ColorId);
122 SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); 122 SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);
123 if (args->mode & fillBackground_RunMode) { 123 if (args->mode & fillBackground_RunMode) {
124 SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0); 124 SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);
125 } 125 }
126 } 126 }
127 chPos = end_RegExpMatch(&m); 127 chPos = end_RegExpMatch(&m);
@@ -205,9 +205,9 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
205 } 205 }
206 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { 206 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {
207 const iColor clr = get_Color(colorNum); 207 const iColor clr = get_Color(colorNum);
208 SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); 208 SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);
209 if (args->mode & fillBackground_RunMode) { 209 if (args->mode & fillBackground_RunMode) {
210 SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0); 210 SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);
211 } 211 }
212 } 212 }
213 prevCh = 0; 213 prevCh = 0;
@@ -311,9 +311,9 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
311 if (args->mode & fillBackground_RunMode) { 311 if (args->mode & fillBackground_RunMode) {
312 /* Alpha blending looks much better if the RGB components don't change in 312 /* Alpha blending looks much better if the RGB components don't change in
313 the partially transparent pixels. */ 313 the partially transparent pixels. */
314 SDL_RenderFillRect(text_.render, &dst); 314 SDL_RenderFillRect(activeText_->render, &dst);
315 } 315 }
316 SDL_RenderCopy(text_.render, text_.cache, &src, &dst); 316 SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst);
317 } 317 }
318 xpos += advance; 318 xpos += advance;
319 if (!isSpace_Char(ch)) { 319 if (!isSpace_Char(ch)) {
diff --git a/src/ui/util.c b/src/ui/util.c
index 721aed2d..38977b96 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -613,6 +613,8 @@ iBool isAction_Widget(const iWidget *d) {
613/*-----------------------------------------------------------------------------------------------*/ 613/*-----------------------------------------------------------------------------------------------*/
614 614
615static iBool isCommandIgnoredByMenus_(const char *cmd) { 615static iBool isCommandIgnoredByMenus_(const char *cmd) {
616 if (equal_Command(cmd, "window.focus.lost") ||
617 equal_Command(cmd, "window.focus.gained")) return iTrue;
616 /* TODO: Perhaps a common way of indicating which commands are notifications and should not 618 /* TODO: Perhaps a common way of indicating which commands are notifications and should not
617 be reacted to by menus? */ 619 be reacted to by menus? */
618 return equal_Command(cmd, "media.updated") || 620 return equal_Command(cmd, "media.updated") ||
@@ -810,6 +812,10 @@ static void updateMenuItemFonts_Widget_(iWidget *d) {
810 } 812 }
811} 813}
812 814
815iLocalDef iBool isUsingMenuPopupWindows_(void) {
816 return deviceType_App() == desktop_AppDeviceType;
817}
818
813void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { 819void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
814 const iRect rootRect = rect_Root(d->root); 820 const iRect rootRect = rect_Root(d->root);
815 const iInt2 rootSize = rootRect.size; 821 const iInt2 rootSize = rootRect.size;
@@ -822,6 +828,26 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
822 processEvents_App(postedEventsOnly_AppEventMode); 828 processEvents_App(postedEventsOnly_AppEventMode);
823 setFlags_Widget(d, hidden_WidgetFlag, iFalse); 829 setFlags_Widget(d, hidden_WidgetFlag, iFalse);
824 setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue); 830 setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue);
831 if (isUsingMenuPopupWindows_()) {
832 if (postCommands) {
833 postCommand_Widget(d, "menu.opened");
834 }
835 updateMenuItemFonts_Widget_(d);
836 iRoot *oldRoot = current_Root();
837 setFlags_Widget(d, keepOnTop_WidgetFlag, iFalse);
838 setUserData_Object(d, parent_Widget(d));
839 removeChild_Widget(parent_Widget(d), d); /* we'll borrow the widget for a while */
840 iInt2 mousePos;
841 SDL_GetGlobalMouseState(&mousePos.x, &mousePos.y);
842 iWindow *win = newPopup_Window(sub_I2(mousePos, divi_I2(gap2_UI, 2)), d);
843 SDL_SetWindowTitle(win->win, "Menu");
844 addPopup_App(win); /* window takes the widget */
845 SDL_ShowWindow(win->win);
846 draw_Window(win);
847 setCurrent_Window(mainWindow_App());
848 setCurrent_Root(oldRoot);
849 return;
850 }
825 raise_Widget(d); 851 raise_Widget(d);
826 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); 852 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse);
827 if (isPortraitPhone) { 853 if (isPortraitPhone) {
@@ -836,7 +862,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
836 arrange_Widget(d); 862 arrange_Widget(d);
837 if (isPortraitPhone) { 863 if (isPortraitPhone) {
838 if (isSlidePanel) { 864 if (isSlidePanel) {
839 d->rect.pos = zero_I2(); //neg_I2(bounds_Widget(parent_Widget(d)).pos); 865 d->rect.pos = zero_I2();
840 } 866 }
841 else { 867 else {
842 d->rect.pos = init_I2(0, rootSize.y); 868 d->rect.pos = init_I2(0, rootSize.y);
@@ -856,7 +882,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
856 float l, t, r, b; 882 float l, t, r, b;
857 safeAreaInsets_iOS(&l, &t, &r, &b); 883 safeAreaInsets_iOS(&l, &t, &r, &b);
858 topExcess += t; 884 topExcess += t;
859 bottomExcess += iMax(b, get_Window()->keyboardHeight); 885 bottomExcess += iMax(b, get_MainWindow()->keyboardHeight);
860 leftExcess += l; 886 leftExcess += l;
861 rightExcess += r; 887 rightExcess += r;
862 } 888 }
@@ -884,6 +910,18 @@ void closeMenu_Widget(iWidget *d) {
884 if (d == NULL || flags_Widget(d) & hidden_WidgetFlag) { 910 if (d == NULL || flags_Widget(d) & hidden_WidgetFlag) {
885 return; /* Already closed. */ 911 return; /* Already closed. */
886 } 912 }
913 if (isUsingMenuPopupWindows_()) {
914 iWindow *win = window_Widget(d);
915 iAssert(type_Window(win) == popup_WindowType);
916 iWidget *originalParent = userData_Object(d);
917 setUserData_Object(d, NULL);
918 win->roots[0]->widget = NULL;
919 setRoot_Widget(d, originalParent->root);
920 addChild_Widget(originalParent, d);
921 setFlags_Widget(d, keepOnTop_WidgetFlag, iTrue);
922 SDL_HideWindow(win->win);
923 collect_Garbage(win, (iDeleteFunc) delete_Window); /* get rid of it after event processing */
924 }
887 setFlags_Widget(d, hidden_WidgetFlag, iTrue); 925 setFlags_Widget(d, hidden_WidgetFlag, iTrue);
888 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue); 926 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue);
889 postRefresh_App(); 927 postRefresh_App();
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 23c19315..7b33a752 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -271,6 +271,10 @@ iWidget *root_Widget(const iWidget *d) {
271 return d ? d->root->widget : NULL; 271 return d ? d->root->widget : NULL;
272} 272}
273 273
274iWindow *window_Widget(const iAnyObject *d) {
275 return constAs_Widget(d)->root->window;
276}
277
274void showCollapsed_Widget(iWidget *d, iBool show) { 278void showCollapsed_Widget(iWidget *d, iBool show) {
275 const iBool isVisible = !(d->flags & hidden_WidgetFlag); 279 const iBool isVisible = !(d->flags & hidden_WidgetFlag);
276 if ((isVisible && !show) || (!isVisible && show)) { 280 if ((isVisible && !show) || (!isVisible && show)) {
@@ -979,11 +983,10 @@ void unhover_Widget(void) {
979} 983}
980 984
981iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { 985iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
982 //iAssert(d->root == get_Root());
983 if (!d->parent) { 986 if (!d->parent) {
984 if (get_Window()->focus && get_Window()->focus->root == d->root && isKeyboardEvent_(ev)) { 987 if (window_Widget(d)->focus && window_Widget(d)->focus->root == d->root && isKeyboardEvent_(ev)) {
985 /* Root dispatches keyboard events directly to the focused widget. */ 988 /* Root dispatches keyboard events directly to the focused widget. */
986 if (dispatchEvent_Widget(get_Window()->focus, ev)) { 989 if (dispatchEvent_Widget(window_Widget(d)->focus, ev)) {
987 return iTrue; 990 return iTrue;
988 } 991 }
989 } 992 }
@@ -1012,7 +1015,8 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
1012 } 1015 }
1013 } 1016 }
1014 else if (ev->type == SDL_MOUSEMOTION && 1017 else if (ev->type == SDL_MOUSEMOTION &&
1015 (!get_Window()->hover || hasParent_Widget(d, get_Window()->hover)) && 1018 ev->motion.windowID == SDL_GetWindowID(window_Widget(d)->win) &&
1019 (!window_Widget(d)->hover || hasParent_Widget(d, window_Widget(d)->hover)) &&
1016 flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag && 1020 flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag &&
1017 ~flags_Widget(d) & disabled_WidgetFlag) { 1021 ~flags_Widget(d) & disabled_WidgetFlag) {
1018 if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) { 1022 if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) {
@@ -1031,11 +1035,11 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
1031 iReverseForEach(ObjectList, i, d->children) { 1035 iReverseForEach(ObjectList, i, d->children) {
1032 iWidget *child = as_Widget(i.object); 1036 iWidget *child = as_Widget(i.object);
1033 //iAssert(child->root == d->root); 1037 //iAssert(child->root == d->root);
1034 if (child == get_Window()->focus && isKeyboardEvent_(ev)) { 1038 if (child == window_Widget(d)->focus && isKeyboardEvent_(ev)) {
1035 continue; /* Already dispatched. */ 1039 continue; /* Already dispatched. */
1036 } 1040 }
1037 if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) { 1041 if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) {
1038 /* Already dispatched. */ 1042 /* Already dispatched. */
1039 continue; 1043 continue;
1040 } 1044 }
1041 if (dispatchEvent_Widget(child, ev)) { 1045 if (dispatchEvent_Widget(child, ev)) {
@@ -1050,7 +1054,7 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
1050#endif 1054#endif
1051#if 0 1055#if 0
1052 if (ev->type == SDL_MOUSEMOTION) { 1056 if (ev->type == SDL_MOUSEMOTION) {
1053 printf("[%p] %s:'%s' (on top) ate the motion\n", 1057 printf("[%p] %s:'%s' ate the motion\n",
1054 child, class_Widget(child)->name, 1058 child, class_Widget(child)->name,
1055 cstr_String(id_Widget(child))); 1059 cstr_String(id_Widget(child)));
1056 fflush(stdout); 1060 fflush(stdout);
@@ -1246,7 +1250,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
1246 ev->button.x, 1250 ev->button.x,
1247 ev->button.y); 1251 ev->button.y);
1248 } 1252 }
1249 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); 1253 setCursor_Window(window_Widget(d), SDL_SYSTEM_CURSOR_ARROW);
1250 return iTrue; 1254 return iTrue;
1251 } 1255 }
1252 return iFalse; 1256 return iFalse;
@@ -1270,6 +1274,7 @@ iLocalDef iBool isDrawn_Widget_(const iWidget *d) {
1270void drawLayerEffects_Widget(const iWidget *d) { 1274void drawLayerEffects_Widget(const iWidget *d) {
1271 /* Layered effects are not buffered, so they are drawn here separately. */ 1275 /* Layered effects are not buffered, so they are drawn here separately. */
1272 iAssert(isDrawn_Widget_(d)); 1276 iAssert(isDrawn_Widget_(d));
1277 iAssert(window_Widget(d) == get_Window());
1273 iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0; 1278 iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0;
1274 iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag; 1279 iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag;
1275 if (deviceType_App() == phone_AppDeviceType) { 1280 if (deviceType_App() == phone_AppDeviceType) {
@@ -1539,6 +1544,7 @@ static void endBufferDraw_Widget_(const iWidget *d) {
1539} 1544}
1540 1545
1541void draw_Widget(const iWidget *d) { 1546void draw_Widget(const iWidget *d) {
1547 iAssert(window_Widget(d) == get_Window());
1542 if (!isDrawn_Widget_(d)) { 1548 if (!isDrawn_Widget_(d)) {
1543 if (d->drawBuf) { 1549 if (d->drawBuf) {
1544// printf("[%p] drawBuffer released\n", d); 1550// printf("[%p] drawBuffer released\n", d);
@@ -1820,7 +1826,17 @@ iBool equalWidget_Command(const char *cmd, const iWidget *widget, const char *ch
1820 if (equal_Command(cmd, checkCommand)) { 1826 if (equal_Command(cmd, checkCommand)) {
1821 const iWidget *src = pointer_Command(cmd); 1827 const iWidget *src = pointer_Command(cmd);
1822 iAssert(!src || strstr(cmd, " ptr:")); 1828 iAssert(!src || strstr(cmd, " ptr:"));
1823 return src == widget || hasParent_Widget(src, widget); 1829 if (src == widget || hasParent_Widget(src, widget)) {
1830 return iTrue;
1831 }
1832// if (src && type_Window(window_Widget(src)) == popup_WindowType) {
1833// /* Special case: command was emitted from a popup widget. The popup root widget actually
1834// belongs to someone else. */
1835// iWidget *realParent = userData_Object(src->root->widget);
1836// iAssert(realParent);
1837// iAssert(isInstance_Object(realParent, &Class_Widget));
1838// return realParent == widget || hasParent_Widget(realParent, widget);
1839// }
1824 } 1840 }
1825 return iFalse; 1841 return iFalse;
1826} 1842}
@@ -1962,6 +1978,10 @@ void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) {
1962 } 1978 }
1963 if (!isGlobal) { 1979 if (!isGlobal) {
1964 iAssert(isInstance_Object(d, &Class_Widget)); 1980 iAssert(isInstance_Object(d, &Class_Widget));
1981 if (type_Window(window_Widget(d)) == popup_WindowType) {
1982 postCommandf_Root(((const iWidget *) d)->root, "cancel popup:1 ptr:%p", d);
1983 d = userData_Object(root_Widget(d));
1984 }
1965 appendFormat_String(&str, " ptr:%p", d); 1985 appendFormat_String(&str, " ptr:%p", d);
1966 } 1986 }
1967 postCommandString_Root(((const iWidget *) d)->root, &str); 1987 postCommandString_Root(((const iWidget *) d)->root, &str);
diff --git a/src/ui/widget.h b/src/ui/widget.h
index 7491cb79..0eab69c1 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -34,7 +34,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
34#include <the_Foundation/string.h> 34#include <the_Foundation/string.h>
35#include <SDL_events.h> 35#include <SDL_events.h>
36 36
37iDeclareType(Root) /* each widget is associated with a Root */ 37iDeclareType(Root) /* each widget is associated with a Root */
38iDeclareType(Window) /* each Root is inside a Window */
38 39
39#define iDeclareWidgetClass(className) \ 40#define iDeclareWidgetClass(className) \
40 iDeclareType(className); \ 41 iDeclareType(className); \
@@ -185,6 +186,7 @@ void releaseChildren_Widget (iWidget *);
185 - inner: 0,0 is at the top left corner of the widget */ 186 - inner: 0,0 is at the top left corner of the widget */
186 187
187iWidget * root_Widget (const iWidget *); 188iWidget * root_Widget (const iWidget *);
189iWindow * window_Widget (const iAnyObject *);
188const iString * id_Widget (const iWidget *); 190const iString * id_Widget (const iWidget *);
189int64_t flags_Widget (const iWidget *); 191int64_t flags_Widget (const iWidget *);
190iRect bounds_Widget (const iWidget *); /* outer bounds */ 192iRect bounds_Widget (const iWidget *); /* outer bounds */
diff --git a/src/ui/window.c b/src/ui/window.c
index 92125d81..e9a34ace 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -57,7 +57,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
57#include "stb_image.h" 57#include "stb_image.h"
58#include "stb_image_resize.h" 58#include "stb_image_resize.h"
59 59
60static iWindow *theWindow_ = NULL; 60static iWindow * theWindow_;
61static iMainWindow *theMainWindow_;
61 62
62#if defined (iPlatformApple) || defined (iPlatformLinux) || defined (iPlatformOther) 63#if defined (iPlatformApple) || defined (iPlatformLinux) || defined (iPlatformOther)
63static float initialUiScale_ = 1.0f; 64static float initialUiScale_ = 1.0f;
@@ -67,6 +68,9 @@ static float initialUiScale_ = 1.1f;
67 68
68static iBool isOpenGLRenderer_; 69static iBool isOpenGLRenderer_;
69 70
71iDefineTypeConstructionArgs(Window,
72 (enum iWindowType type, iRect rect, uint32_t flags),
73 type, rect, flags)
70iDefineTypeConstructionArgs(MainWindow, (iRect rect), rect) 74iDefineTypeConstructionArgs(MainWindow, (iRect rect), rect)
71 75
72/* TODO: Define menus per platform. */ 76/* TODO: Define menus per platform. */
@@ -205,6 +209,7 @@ static void setupUserInterface_MainWindow(iMainWindow *d) {
205#endif 209#endif
206 /* One root is created by default. */ 210 /* One root is created by default. */
207 d->base.roots[0] = new_Root(); 211 d->base.roots[0] = new_Root();
212 d->base.roots[0]->window = as_Window(d);
208 setCurrent_Root(d->base.roots[0]); 213 setCurrent_Root(d->base.roots[0]);
209 createUserInterface_Root(d->base.roots[0]); 214 createUserInterface_Root(d->base.roots[0]);
210 setCurrent_Root(NULL); 215 setCurrent_Root(NULL);
@@ -409,7 +414,6 @@ void init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags)
409 d->mouseGrab = NULL; 414 d->mouseGrab = NULL;
410 d->focus = NULL; 415 d->focus = NULL;
411 d->pendingCursor = NULL; 416 d->pendingCursor = NULL;
412 d->isDrawFrozen = iTrue;
413 d->isExposed = iFalse; 417 d->isExposed = iFalse;
414 d->isMinimized = iFalse; 418 d->isMinimized = iFalse;
415 d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */ 419 d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */
@@ -441,9 +445,27 @@ void init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags)
441 d->uiScale = initialUiScale_; 445 d->uiScale = initialUiScale_;
442 /* TODO: Ratios, scales, and metrics must be window-specific, not global. */ 446 /* TODO: Ratios, scales, and metrics must be window-specific, not global. */
443 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); 447 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale);
448 d->text = new_Text(d->render);
449}
450
451static void deinitRoots_Window_(iWindow *d) {
452 iRecycle();
453 iForIndices(i, d->roots) {
454 if (d->roots[i]) {
455 setCurrent_Root(d->roots[i]);
456 delete_Root(d->roots[i]);
457 d->roots[i] = NULL;
458 }
459 }
460 setCurrent_Root(NULL);
444} 461}
445 462
446void deinit_Window(iWindow *d) { 463void deinit_Window(iWindow *d) {
464 if (d->type == popup_WindowType) {
465 removePopup_App(d);
466 }
467 deinitRoots_Window_(d);
468 delete_Text(d->text);
447 SDL_DestroyRenderer(d->render); 469 SDL_DestroyRenderer(d->render);
448 SDL_DestroyWindow(d->win); 470 SDL_DestroyWindow(d->win);
449 iForIndices(i, d->cursors) { 471 iForIndices(i, d->cursors) {
@@ -455,6 +477,7 @@ void deinit_Window(iWindow *d) {
455 477
456void init_MainWindow(iMainWindow *d, iRect rect) { 478void init_MainWindow(iMainWindow *d, iRect rect) {
457 theWindow_ = &d->base; 479 theWindow_ = &d->base;
480 theMainWindow_ = d;
458 uint32_t flags = 0; 481 uint32_t flags = 0;
459#if defined (iPlatformAppleDesktop) 482#if defined (iPlatformAppleDesktop)
460 SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); 483 SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl");
@@ -465,13 +488,15 @@ void init_MainWindow(iMainWindow *d, iRect rect) {
465#endif 488#endif
466 SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); 489 SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
467 init_Window(&d->base, main_WindowType, rect, flags); 490 init_Window(&d->base, main_WindowType, rect, flags);
468 d->splitMode = d->pendingSplitMode = 0; 491 d->isDrawFrozen = iTrue;
469 d->pendingSplitUrl = new_String(); 492 d->splitMode = 0;
470 d->place.initialPos = rect.pos; 493 d->pendingSplitMode = 0;
471 d->place.normalRect = rect; 494 d->pendingSplitUrl = new_String();
495 d->place.initialPos = rect.pos;
496 d->place.normalRect = rect;
472 d->place.lastNotifiedSize = zero_I2(); 497 d->place.lastNotifiedSize = zero_I2();
473 d->place.snap = 0; 498 d->place.snap = 0;
474 d->keyboardHeight = 0; 499 d->keyboardHeight = 0;
475#if defined(iPlatformMobile) 500#if defined(iPlatformMobile)
476 const iInt2 minSize = zero_I2(); /* windows aren't independently resizable */ 501 const iInt2 minSize = zero_I2(); /* windows aren't independently resizable */
477#else 502#else
@@ -510,9 +535,9 @@ void init_MainWindow(iMainWindow *d, iRect rect) {
510 } 535 }
511#endif 536#endif
512#if defined (iPlatformAppleMobile) 537#if defined (iPlatformAppleMobile)
513 setupWindow_iOS(d); 538 setupWindow_iOS(as_Window(d));
514#endif 539#endif
515 init_Text(d->base.render); 540 setCurrent_Text(d->base.text);
516 SDL_GetRendererOutputSize(d->base.render, &d->base.size.x, &d->base.size.y); 541 SDL_GetRendererOutputSize(d->base.render, &d->base.size.x, &d->base.size.y);
517 setupUserInterface_MainWindow(d); 542 setupUserInterface_MainWindow(d);
518 postCommand_App("~bindings.changed"); /* update from bindings */ 543 postCommand_App("~bindings.changed"); /* update from bindings */
@@ -538,24 +563,15 @@ void init_MainWindow(iMainWindow *d, iRect rect) {
538#endif 563#endif
539} 564}
540 565
541static void deinitRoots_Window_(iWindow *d) {
542 iRecycle();
543 iForIndices(i, d->roots) {
544 if (d->roots[i]) {
545 setCurrent_Root(d->roots[i]);
546 deinit_Root(d->roots[i]);
547 }
548 }
549 setCurrent_Root(NULL);
550}
551
552void deinit_MainWindow(iMainWindow *d) { 566void deinit_MainWindow(iMainWindow *d) {
553 deinitRoots_Window_(as_Window(d)); 567 deinitRoots_Window_(as_Window(d));
554 if (theWindow_ == as_Window(d)) { 568 if (theWindow_ == as_Window(d)) {
555 theWindow_ = NULL; 569 theWindow_ = NULL;
556 } 570 }
571 if (theMainWindow_ == d) {
572 theMainWindow_ = NULL;
573 }
557 delete_String(d->pendingSplitUrl); 574 delete_String(d->pendingSplitUrl);
558 deinit_Text();
559 deinit_Window(&d->base); 575 deinit_Window(&d->base);
560} 576}
561 577
@@ -592,7 +608,7 @@ iRoot *otherRoot_Window(const iWindow *d, iRoot *root) {
592static void invalidate_MainWindow_(iMainWindow *d, iBool forced) { 608static void invalidate_MainWindow_(iMainWindow *d, iBool forced) {
593 if (d && (!d->base.isInvalidated || forced)) { 609 if (d && (!d->base.isInvalidated || forced)) {
594 d->base.isInvalidated = iTrue; 610 d->base.isInvalidated = iTrue;
595 resetFonts_Text(); 611 resetFonts_Text(text_Window(d));
596 postCommand_App("theme.changed auto:1"); /* forces UI invalidation */ 612 postCommand_App("theme.changed auto:1"); /* forces UI invalidation */
597 } 613 }
598} 614}
@@ -607,7 +623,7 @@ void invalidate_Window(iAnyWindow *d) {
607} 623}
608 624
609static iBool isNormalPlacement_MainWindow_(const iMainWindow *d) { 625static iBool isNormalPlacement_MainWindow_(const iMainWindow *d) {
610 if (d->base.isDrawFrozen) return iFalse; 626 if (d->isDrawFrozen) return iFalse;
611#if defined (iPlatformApple) 627#if defined (iPlatformApple)
612 /* Maximized mode is not special on macOS. */ 628 /* Maximized mode is not special on macOS. */
613 if (snap_MainWindow(d) == maximized_WindowSnap) { 629 if (snap_MainWindow(d) == maximized_WindowSnap) {
@@ -655,7 +671,7 @@ static iBool unsnap_MainWindow_(iMainWindow *d, const iInt2 *newPos) {
655static void notifyMetricsChange_Window_(const iWindow *d) { 671static void notifyMetricsChange_Window_(const iWindow *d) {
656 /* Dynamic UI metrics change. Widgets need to update themselves. */ 672 /* Dynamic UI metrics change. Widgets need to update themselves. */
657 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); 673 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale);
658 resetFonts_Text(); 674 resetFonts_Text(d->text);
659 postCommand_App("metrics.changed"); 675 postCommand_App("metrics.changed");
660} 676}
661 677
@@ -676,6 +692,41 @@ static void checkPixelRatioChange_Window_(iWindow *d) {
676 } 692 }
677} 693}
678 694
695static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
696 if (ev->windowID != SDL_GetWindowID(d->win)) {
697 return iFalse;
698 }
699 switch (ev->event) {
700 case SDL_WINDOWEVENT_EXPOSED:
701 d->isExposed = iTrue;
702 postRefresh_App();
703 return iTrue;
704 case SDL_WINDOWEVENT_RESTORED:
705 case SDL_WINDOWEVENT_SHOWN:
706 postRefresh_App();
707 return iTrue;
708 case SDL_WINDOWEVENT_FOCUS_LOST:
709 /* Popup windows are currently only used for menus. */
710 closeMenu_Widget(d->roots[0]->widget);
711 return iTrue;
712 case SDL_WINDOWEVENT_LEAVE:
713 unhover_Widget();
714 d->isMouseInside = iFalse;
715 //postCommand_App("window.mouse.exited");
716// SDL_SetWindowInputFocus(mainWindow_App()->base.win);
717 printf("mouse leaves popup\n"); fflush(stdout);
718 //SDL_RaiseWindow(mainWindow_App()->base.win);
719 postRefresh_App();
720 return iTrue;
721 case SDL_WINDOWEVENT_ENTER:
722 d->isMouseInside = iTrue;
723 //postCommand_App("window.mouse.entered");
724 printf("mouse enters popup\n"); fflush(stdout);
725 return iTrue;
726 }
727 return iFalse;
728}
729
679static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) { 730static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) {
680 switch (ev->event) { 731 switch (ev->event) {
681#if defined(iPlatformDesktop) 732#if defined(iPlatformDesktop)
@@ -795,6 +846,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent
795 return iTrue; 846 return iTrue;
796 case SDL_WINDOWEVENT_ENTER: 847 case SDL_WINDOWEVENT_ENTER:
797 d->base.isMouseInside = iTrue; 848 d->base.isMouseInside = iTrue;
849 SDL_SetWindowInputFocus(d->base.win);
798 postCommand_App("window.mouse.entered"); 850 postCommand_App("window.mouse.entered");
799 return iTrue; 851 return iTrue;
800 case SDL_WINDOWEVENT_FOCUS_GAINED: 852 case SDL_WINDOWEVENT_FOCUS_GAINED:
@@ -802,16 +854,16 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent
802 setCapsLockDown_Keys(iFalse); 854 setCapsLockDown_Keys(iFalse);
803 postCommand_App("window.focus.gained"); 855 postCommand_App("window.focus.gained");
804 d->base.isExposed = iTrue; 856 d->base.isExposed = iTrue;
805#if !defined(iPlatformDesktop) 857#if !defined (iPlatformDesktop)
806 /* Returned to foreground, may have lost buffered content. */ 858 /* Returned to foreground, may have lost buffered content. */
807 invalidate_Window_(d, iTrue); 859 invalidate_MainWindow_(d, iTrue);
808 postCommand_App("window.unfreeze"); 860 postCommand_App("window.unfreeze");
809#endif 861#endif
810 return iFalse; 862 return iFalse;
811 case SDL_WINDOWEVENT_FOCUS_LOST: 863 case SDL_WINDOWEVENT_FOCUS_LOST:
812 postCommand_App("window.focus.lost"); 864 postCommand_App("window.focus.lost");
813#if !defined(iPlatformDesktop) 865#if !defined (iPlatformDesktop)
814 setFreezeDraw_Window(d, iTrue); 866 setFreezeDraw_MainWindow(d, iTrue);
815#endif 867#endif
816 return iFalse; 868 return iFalse;
817 case SDL_WINDOWEVENT_TAKE_FOCUS: 869 case SDL_WINDOWEVENT_TAKE_FOCUS:
@@ -831,8 +883,8 @@ static void applyCursor_Window_(iWindow *d) {
831 } 883 }
832} 884}
833 885
834iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) { 886iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
835 iWindow *w = as_Window(d); 887 iMainWindow *mw = (type_Window(d) == main_WindowType ? as_MainWindow(d) : NULL);
836 switch (ev->type) { 888 switch (ev->type) {
837#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 889#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
838 case SDL_SYSWMEVENT: { 890 case SDL_SYSWMEVENT: {
@@ -845,19 +897,26 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {
845 } 897 }
846#endif 898#endif
847 case SDL_WINDOWEVENT: { 899 case SDL_WINDOWEVENT: {
848 return handleWindowEvent_MainWindow_(d, &ev->window); 900 if (mw) {
901 return handleWindowEvent_MainWindow_(mw, &ev->window);
902 }
903 else {
904 return handleWindowEvent_Window_(d, &ev->window);
905 }
849 } 906 }
850 case SDL_RENDER_TARGETS_RESET: 907 case SDL_RENDER_TARGETS_RESET:
851 case SDL_RENDER_DEVICE_RESET: { 908 case SDL_RENDER_DEVICE_RESET: {
852 invalidate_MainWindow_(d, iTrue /* force full reset */); 909 if (mw) {
910 invalidate_MainWindow_(mw, iTrue /* force full reset */);
911 }
853 break; 912 break;
854 } 913 }
855 default: { 914 default: {
856 SDL_Event event = *ev; 915 SDL_Event event = *ev;
857 if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze")) { 916 if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze")) {
858 d->base.isDrawFrozen = iFalse; 917 mw->isDrawFrozen = iFalse;
859 if (SDL_GetWindowFlags(w->win) & SDL_WINDOW_HIDDEN) { 918 if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) {
860 SDL_ShowWindow(w->win); 919 SDL_ShowWindow(d->win);
861 } 920 }
862 postRefresh_App(); 921 postRefresh_App();
863 postCommand_App("media.player.update"); /* in case a player needs updating */ 922 postCommand_App("media.player.update"); /* in case a player needs updating */
@@ -866,35 +925,35 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {
866 if (processEvent_Touch(&event)) { 925 if (processEvent_Touch(&event)) {
867 return iTrue; 926 return iTrue;
868 } 927 }
869 if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->base.focusGainedAt < 10) { 928 if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->focusGainedAt < 10) {
870 /* Suspiciously close to when input focus was received. For example under openbox, 929 /* Suspiciously close to when input focus was received. For example under openbox,
871 closing xterm with Ctrl+D will cause the keydown event to "spill" over to us. 930 closing xterm with Ctrl+D will cause the keydown event to "spill" over to us.
872 As a workaround, ignore these events. */ 931 As a workaround, ignore these events. */
873 return iTrue; /* won't go to bindings, either */ 932 return iTrue; /* won't go to bindings, either */
874 } 933 }
875 if (event.type == SDL_MOUSEBUTTONDOWN && d->base.ignoreClick) { 934 if (event.type == SDL_MOUSEBUTTONDOWN && d->ignoreClick) {
876 d->base.ignoreClick = iFalse; 935 d->ignoreClick = iFalse;
877 return iTrue; 936 return iTrue;
878 } 937 }
879 /* Map mouse pointer coordinate to our coordinate system. */ 938 /* Map mouse pointer coordinate to our coordinate system. */
880 if (event.type == SDL_MOUSEMOTION) { 939 if (event.type == SDL_MOUSEMOTION) {
881 setCursor_Window(w, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */ 940 setCursor_Window(d, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */
882 const iInt2 pos = coord_Window(w, event.motion.x, event.motion.y); 941 const iInt2 pos = coord_Window(d, event.motion.x, event.motion.y);
883 event.motion.x = pos.x; 942 event.motion.x = pos.x;
884 event.motion.y = pos.y; 943 event.motion.y = pos.y;
885 } 944 }
886 else if (event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) { 945 else if (event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {
887 const iInt2 pos = coord_Window(w, event.button.x, event.button.y); 946 const iInt2 pos = coord_Window(d, event.button.x, event.button.y);
888 event.button.x = pos.x; 947 event.button.x = pos.x;
889 event.button.y = pos.y; 948 event.button.y = pos.y;
890 if (event.type == SDL_MOUSEBUTTONDOWN) { 949 if (event.type == SDL_MOUSEBUTTONDOWN) {
891 /* Button clicks will change keyroot. */ 950 /* Button clicks will change keyroot. */
892 if (numRoots_Window(w) > 1) { 951 if (numRoots_Window(d) > 1) {
893 const iInt2 click = init_I2(event.button.x, event.button.y); 952 const iInt2 click = init_I2(event.button.x, event.button.y);
894 iForIndices(i, w->roots) { 953 iForIndices(i, d->roots) {
895 iRoot *root = w->roots[i]; 954 iRoot *root = d->roots[i];
896 if (root != w->keyRoot && contains_Rect(rect_Root(root), click)) { 955 if (root != d->keyRoot && contains_Rect(rect_Root(root), click)) {
897 setKeyRoot_Window(w, root); 956 setKeyRoot_Window(d, root);
898 break; 957 break;
899 } 958 }
900 } 959 }
@@ -909,13 +968,13 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {
909 event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) { 968 event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {
910 if (mouseGrab_Widget()) { 969 if (mouseGrab_Widget()) {
911 iWidget *grabbed = mouseGrab_Widget(); 970 iWidget *grabbed = mouseGrab_Widget();
912 setCurrent_Root(findRoot_Window(w, grabbed)); 971 setCurrent_Root(findRoot_Window(d, grabbed));
913 wasUsed = dispatchEvent_Widget(grabbed, &event); 972 wasUsed = dispatchEvent_Widget(grabbed, &event);
914 } 973 }
915 } 974 }
916 /* Dispatch the event to the tree of widgets. */ 975 /* Dispatch the event to the tree of widgets. */
917 if (!wasUsed) { 976 if (!wasUsed) {
918 wasUsed = dispatchEvent_Window(w, &event); 977 wasUsed = dispatchEvent_Window(d, &event);
919 } 978 }
920 if (!wasUsed) { 979 if (!wasUsed) {
921 /* As a special case, clicking the middle mouse button can be used for pasting 980 /* As a special case, clicking the middle mouse button can be used for pasting
@@ -928,35 +987,35 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {
928 paste.key.keysym.mod = KMOD_PRIMARY; 987 paste.key.keysym.mod = KMOD_PRIMARY;
929 paste.key.state = SDL_PRESSED; 988 paste.key.state = SDL_PRESSED;
930 paste.key.timestamp = SDL_GetTicks(); 989 paste.key.timestamp = SDL_GetTicks();
931 wasUsed = dispatchEvent_Window(w, &paste); 990 wasUsed = dispatchEvent_Window(d, &paste);
932 } 991 }
933 if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) { 992 if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) {
934 if (postContextClick_Window(w, &event.button)) { 993 if (postContextClick_Window(d, &event.button)) {
935 wasUsed = iTrue; 994 wasUsed = iTrue;
936 } 995 }
937 } 996 }
938 } 997 }
939 if (isMetricsChange_UserEvent(&event)) { 998 if (isMetricsChange_UserEvent(&event)) {
940 iForIndices(i, w->roots) { 999 iForIndices(i, d->roots) {
941 updateMetrics_Root(w->roots[i]); 1000 updateMetrics_Root(d->roots[i]);
942 } 1001 }
943 } 1002 }
944 if (isCommand_UserEvent(&event, "lang.changed")) { 1003 if (isCommand_UserEvent(&event, "lang.changed") && mw) {
945#if defined (iHaveNativeMenus) 1004#if defined (iHaveNativeMenus)
946 /* Retranslate the menus. */ 1005 /* Retranslate the menus. */
947 removeMacMenus_(); 1006 removeMacMenus_();
948 insertMacMenus_(); 1007 insertMacMenus_();
949#endif 1008#endif
950 invalidate_Window(w); 1009 invalidate_Window(d);
951 iForIndices(i, w->roots) { 1010 iForIndices(i, d->roots) {
952 if (w->roots[i]) { 1011 if (d->roots[i]) {
953 updatePreferencesLayout_Widget(findChild_Widget(w->roots[i]->widget, "prefs")); 1012 updatePreferencesLayout_Widget(findChild_Widget(d->roots[i]->widget, "prefs"));
954 arrange_Widget(w->roots[i]->widget); 1013 arrange_Widget(d->roots[i]->widget);
955 } 1014 }
956 } 1015 }
957 } 1016 }
958 if (event.type == SDL_MOUSEMOTION) { 1017 if (event.type == SDL_MOUSEMOTION) {
959 applyCursor_Window_(w); 1018 applyCursor_Window_(d);
960 } 1019 }
961 return wasUsed; 1020 return wasUsed;
962 } 1021 }
@@ -1003,6 +1062,9 @@ iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) {
1003 coord_MouseWheelEvent(&ev->wheel))) { 1062 coord_MouseWheelEvent(&ev->wheel))) {
1004 continue; /* Only process the event in the relevant split. */ 1063 continue; /* Only process the event in the relevant split. */
1005 } 1064 }
1065 if (!root->widget) {
1066 continue;
1067 }
1006 setCurrent_Root(root); 1068 setCurrent_Root(root);
1007 const iBool wasUsed = dispatchEvent_Widget(root->widget, ev); 1069 const iBool wasUsed = dispatchEvent_Widget(root->widget, ev);
1008 if (wasUsed) { 1070 if (wasUsed) {
@@ -1044,11 +1106,40 @@ iBool postContextClick_Window(iWindow *d, const SDL_MouseButtonEvent *ev) {
1044 return iFalse; 1106 return iFalse;
1045} 1107}
1046 1108
1109void draw_Window(iWindow *d) {
1110 if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) {
1111 return;
1112 }
1113 iPaint p;
1114 init_Paint(&p);
1115 iRoot *root = d->roots[0];
1116 setCurrent_Root(root);
1117 unsetClip_Paint(&p); /* update clip to full window */
1118 const iColor back = get_Color(uiBackground_ColorId);
1119 SDL_SetRenderDrawColor(d->render, back.r, back.g, back.b, 255);
1120 SDL_RenderClear(d->render);
1121 d->frameTime = SDL_GetTicks();
1122 if (isExposed_Window(d)) {
1123 d->isInvalidated = iFalse;
1124 extern int drawCount_;
1125 drawRoot_Widget(root->widget);
1126#if !defined (NDEBUG)
1127 draw_Text(defaultBold_FontId, safeRect_Root(root).pos, red_ColorId, "%d", drawCount_);
1128 drawCount_ = 0;
1129#endif
1130 }
1131// drawRectThickness_Paint(&p, (iRect){ zero_I2(), sub_I2(d->size, one_I2()) }, gap_UI / 4, uiSeparator_ColorId);
1132 setCurrent_Root(NULL);
1133 SDL_RenderPresent(d->render);
1134}
1135
1047void draw_MainWindow(iMainWindow *d) { 1136void draw_MainWindow(iMainWindow *d) {
1137 /* TODO: Try to make this a specialization of `draw_Window`? */
1048 iWindow *w = as_Window(d); 1138 iWindow *w = as_Window(d);
1049 if (w->isDrawFrozen) { 1139 if (d->isDrawFrozen) {
1050 return; 1140 return;
1051 } 1141 }
1142 setCurrent_Text(d->base.text);
1052 /* Check if root needs resizing. */ { 1143 /* Check if root needs resizing. */ {
1053 iInt2 renderSize; 1144 iInt2 renderSize;
1054 SDL_GetRendererOutputSize(w->render, &renderSize.x, &renderSize.y); 1145 SDL_GetRendererOutputSize(w->render, &renderSize.x, &renderSize.y);
@@ -1180,7 +1271,7 @@ void setUiScale_Window(iWindow *d, float uiScale) {
1180 } 1271 }
1181} 1272}
1182 1273
1183void setFreezeDraw_Window(iWindow *d, iBool freezeDraw) { 1274void setFreezeDraw_MainWindow(iMainWindow *d, iBool freezeDraw) {
1184 d->isDrawFrozen = freezeDraw; 1275 d->isDrawFrozen = freezeDraw;
1185} 1276}
1186 1277
@@ -1231,8 +1322,23 @@ iWindow *get_Window(void) {
1231 return theWindow_; 1322 return theWindow_;
1232} 1323}
1233 1324
1325void setCurrent_Window(iAnyWindow *d) {
1326 theWindow_ = d;
1327 if (type_Window(d) == main_WindowType) {
1328 theMainWindow_ = d;
1329 }
1330 if (d) {
1331 setCurrent_Text(theWindow_->text);
1332 setCurrent_Root(theWindow_->keyRoot);
1333 }
1334 else {
1335 setCurrent_Text(NULL);
1336 setCurrent_Root(NULL);
1337 }
1338}
1339
1234iMainWindow *get_MainWindow(void) { 1340iMainWindow *get_MainWindow(void) {
1235 return as_MainWindow(theWindow_); 1341 return theMainWindow_;
1236} 1342}
1237 1343
1238iBool isOpenGLRenderer_Window(void) { 1344iBool isOpenGLRenderer_Window(void) {
@@ -1272,7 +1378,7 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) {
1272 iAssert(current_Root() == NULL); 1378 iAssert(current_Root() == NULL);
1273 if (d->splitMode != splitMode) { 1379 if (d->splitMode != splitMode) {
1274 int oldCount = numRoots_Window(w); 1380 int oldCount = numRoots_Window(w);
1275 setFreezeDraw_Window(w, iTrue); 1381 setFreezeDraw_MainWindow(d, iTrue);
1276 if (oldCount == 2 && splitMode == 0) { 1382 if (oldCount == 2 && splitMode == 0) {
1277 /* Keep references to the tabs of the second root. */ 1383 /* Keep references to the tabs of the second root. */
1278 const iDocumentWidget *curPage = document_Root(w->keyRoot); 1384 const iDocumentWidget *curPage = document_Root(w->keyRoot);
@@ -1311,6 +1417,7 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) {
1311 } 1417 }
1312 w->roots[newRootIndex] = new_Root(); 1418 w->roots[newRootIndex] = new_Root();
1313 w->keyRoot = w->roots[newRootIndex]; 1419 w->keyRoot = w->roots[newRootIndex];
1420 w->keyRoot->window = w;
1314 setCurrent_Root(w->roots[newRootIndex]); 1421 setCurrent_Root(w->roots[newRootIndex]);
1315 createUserInterface_Root(w->roots[newRootIndex]); 1422 createUserInterface_Root(w->roots[newRootIndex]);
1316 if (!isEmpty_String(d->pendingSplitUrl)) { 1423 if (!isEmpty_String(d->pendingSplitUrl)) {
@@ -1471,3 +1578,25 @@ int snap_MainWindow(const iMainWindow *d) {
1471 } 1578 }
1472 return d->place.snap; 1579 return d->place.snap;
1473} 1580}
1581
1582/*----------------------------------------------------------------------------------------------*/
1583
1584iWindow *newPopup_Window(iInt2 screenPos, iWidget *rootWidget) {
1585 arrange_Widget(rootWidget);
1586 iWindow *win =
1587 new_Window(popup_WindowType,
1588 (iRect){ screenPos, divf_I2(rootWidget->rect.size, get_Window()->pixelRatio) },
1589 SDL_WINDOW_ALWAYS_ON_TOP |
1590 SDL_WINDOW_POPUP_MENU |
1591 SDL_WINDOW_SKIP_TASKBAR);
1592#if defined (iPlatformAppleDesktop)
1593 hideTitleBar_MacOS(win); /* make it a borderless window */
1594#endif
1595 iRoot *root = new_Root();
1596 win->roots[0] = root;
1597 win->keyRoot = root;
1598 root->widget = rootWidget;
1599 root->window = win;
1600 setRoot_Widget(rootWidget, root);
1601 return win;
1602}
diff --git a/src/ui/window.h b/src/ui/window.h
index 73e92391..f1827931 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -29,8 +29,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
29#include <SDL_render.h> 29#include <SDL_render.h>
30#include <SDL_video.h> 30#include <SDL_video.h>
31 31
32enum iWindowType {
33 main_WindowType,
34 popup_WindowType,
35};
36
32iDeclareType(MainWindow) 37iDeclareType(MainWindow)
38iDeclareType(Text)
33iDeclareType(Window) 39iDeclareType(Window)
40
41iDeclareTypeConstructionArgs(Window, enum iWindowType type, iRect rect, uint32_t flags)
34iDeclareTypeConstructionArgs(MainWindow, iRect rect) 42iDeclareTypeConstructionArgs(MainWindow, iRect rect)
35 43
36typedef iAny iAnyWindow; 44typedef iAny iAnyWindow;
@@ -71,15 +79,9 @@ enum iWindowSplit {
71 noEvents_WindowSplit = iBit(11), 79 noEvents_WindowSplit = iBit(11),
72}; 80};
73 81
74enum iWindowType {
75 main_WindowType,
76 popup_WindowType,
77};
78
79struct Impl_Window { 82struct Impl_Window {
80 enum iWindowType type; 83 enum iWindowType type;
81 SDL_Window * win; 84 SDL_Window * win;
82 iBool isDrawFrozen; /* avoids premature draws while restoring window state */
83 iBool isExposed; 85 iBool isExposed;
84 iBool isMinimized; 86 iBool isMinimized;
85 iBool isMouseInside; 87 iBool isMouseInside;
@@ -102,11 +104,13 @@ struct Impl_Window {
102 iRoot * roots[2]; /* root widget and UI state; second one is for split mode */ 104 iRoot * roots[2]; /* root widget and UI state; second one is for split mode */
103 iRoot * keyRoot; /* root that has the current keyboard input focus */ 105 iRoot * keyRoot; /* root that has the current keyboard input focus */
104 SDL_Texture * borderShadow; 106 SDL_Texture * borderShadow;
107 iText * text;
105}; 108};
106 109
107struct Impl_MainWindow { 110struct Impl_MainWindow {
108 iWindow base; 111 iWindow base;
109 iWindowPlacement place; 112 iWindowPlacement place;
113 iBool isDrawFrozen; /* avoids premature draws while restoring window state */
110 int splitMode; 114 int splitMode;
111 int pendingSplitMode; 115 int pendingSplitMode;
112 iString * pendingSplitUrl; /* URL to open in a newly opened split */ 116 iString * pendingSplitUrl; /* URL to open in a newly opened split */
@@ -115,7 +119,10 @@ struct Impl_MainWindow {
115}; 119};
116 120
117iLocalDef enum iWindowType type_Window(const iAnyWindow *d) { 121iLocalDef enum iWindowType type_Window(const iAnyWindow *d) {
118 return ((const iWindow *) d)->type; 122 if (d) {
123 return ((const iWindow *) d)->type;
124 }
125 return main_WindowType;
119} 126}
120 127
121uint32_t id_Window (const iWindow *); 128uint32_t id_Window (const iWindow *);
@@ -131,11 +138,11 @@ int numRoots_Window (const iWindow *);
131iRoot * findRoot_Window (const iWindow *, const iWidget *widget); 138iRoot * findRoot_Window (const iWindow *, const iWidget *widget);
132iRoot * otherRoot_Window (const iWindow *, iRoot *root); 139iRoot * otherRoot_Window (const iWindow *, iRoot *root);
133 140
141iBool processEvent_Window (iWindow *, const SDL_Event *);
134iBool dispatchEvent_Window (iWindow *, const SDL_Event *); 142iBool dispatchEvent_Window (iWindow *, const SDL_Event *);
135void invalidate_Window (iAnyWindow *); /* discard all cached graphics */ 143void invalidate_Window (iAnyWindow *); /* discard all cached graphics */
136void draw_Window (iWindow *); 144void draw_Window (iWindow *);
137void setUiScale_Window (iWindow *, float uiScale); 145void setUiScale_Window (iWindow *, float uiScale);
138void setFreezeDraw_Window (iWindow *, iBool freezeDraw);
139void setCursor_Window (iWindow *, int cursor); 146void setCursor_Window (iWindow *, int cursor);
140iBool setKeyRoot_Window (iWindow *, iRoot *root); 147iBool setKeyRoot_Window (iWindow *, iRoot *root);
141iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *); 148iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *);
@@ -143,6 +150,8 @@ iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *);
143iWindow * get_Window (void); 150iWindow * get_Window (void);
144iBool isOpenGLRenderer_Window (void); 151iBool isOpenGLRenderer_Window (void);
145 152
153void setCurrent_Window (iAnyWindow *);
154
146iLocalDef iBool isExposed_Window(const iWindow *d) { 155iLocalDef iBool isExposed_Window(const iWindow *d) {
147 iAssert(d); 156 iAssert(d);
148 return d->isExposed; 157 return d->isExposed;
@@ -158,6 +167,10 @@ iLocalDef const iWindow *constAs_Window(const iAnyWindow *d) {
158 return (const iWindow *) d; 167 return (const iWindow *) d;
159} 168}
160 169
170iLocalDef iText *text_Window(const iAnyWindow *d) {
171 return constAs_Window(d)->text;
172}
173
161/*----------------------------------------------------------------------------------------------*/ 174/*----------------------------------------------------------------------------------------------*/
162 175
163iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) { 176iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) {
@@ -167,6 +180,7 @@ iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) {
167 180
168void setTitle_MainWindow (iMainWindow *, const iString *title); 181void setTitle_MainWindow (iMainWindow *, const iString *title);
169void setSnap_MainWindow (iMainWindow *, int snapMode); 182void setSnap_MainWindow (iMainWindow *, int snapMode);
183void setFreezeDraw_MainWindow (iMainWindow *, iBool freezeDraw);
170void setKeyboardHeight_MainWindow (iMainWindow *, int height); 184void setKeyboardHeight_MainWindow (iMainWindow *, int height);
171void setSplitMode_MainWindow (iMainWindow *, int splitMode); 185void setSplitMode_MainWindow (iMainWindow *, int splitMode);
172void checkPendingSplit_MainWindow (iMainWindow *); 186void checkPendingSplit_MainWindow (iMainWindow *);
@@ -196,3 +210,7 @@ iLocalDef const iMainWindow *constAs_MainWindow(const iAnyWindow *d) {
196 iAssert(type_Window(d) == main_WindowType); 210 iAssert(type_Window(d) == main_WindowType);
197 return (const iMainWindow *) d; 211 return (const iMainWindow *) d;
198} 212}
213
214/*----------------------------------------------------------------------------------------------*/
215
216iWindow * newPopup_Window (iInt2 screenPos, iWidget *rootWidget);