summaryrefslogtreecommitdiff
path: root/src/ui/widget.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/widget.c')
-rw-r--r--src/ui/widget.c454
1 files changed, 401 insertions, 53 deletions
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 4f567989..6b9ee11d 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -40,6 +40,67 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
40# include "../ios.h" 40# include "../ios.h"
41#endif 41#endif
42 42
43struct Impl_WidgetDrawBuffer {
44 SDL_Texture *texture;
45 iInt2 size;
46 iBool isValid;
47 SDL_Texture *oldTarget;
48 iInt2 oldOrigin;
49};
50
51static void init_WidgetDrawBuffer(iWidgetDrawBuffer *d) {
52 d->texture = NULL;
53 d->size = zero_I2();
54 d->isValid = iFalse;
55 d->oldTarget = NULL;
56}
57
58static void deinit_WidgetDrawBuffer(iWidgetDrawBuffer *d) {
59 SDL_DestroyTexture(d->texture);
60}
61
62iDefineTypeConstruction(WidgetDrawBuffer)
63
64static void realloc_WidgetDrawBuffer(iWidgetDrawBuffer *d, SDL_Renderer *render, iInt2 size) {
65 if (!isEqual_I2(d->size, size)) {
66 d->size = size;
67 if (d->texture) {
68 SDL_DestroyTexture(d->texture);
69 }
70 d->texture = SDL_CreateTexture(render,
71 SDL_PIXELFORMAT_RGBA8888,
72 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
73 size.x,
74 size.y);
75 SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND);
76 d->isValid = iFalse;
77 }
78}
79
80static void release_WidgetDrawBuffer(iWidgetDrawBuffer *d) {
81 if (d->texture) {
82 SDL_DestroyTexture(d->texture);
83 d->texture = NULL;
84 }
85 d->size = zero_I2();
86 d->isValid = iFalse;
87}
88
89static iRect boundsForDraw_Widget_(const iWidget *d) {
90 iRect bounds = bounds_Widget(d);
91 if (d->flags & drawBackgroundToBottom_WidgetFlag) {
92 bounds.size.y = iMax(bounds.size.y, size_Root(d->root).y);
93 }
94 return bounds;
95}
96
97static iBool checkDrawBuffer_Widget_(const iWidget *d) {
98 return d->drawBuf && d->drawBuf->isValid &&
99 isEqual_I2(d->drawBuf->size, boundsForDraw_Widget_(d).size);
100}
101
102/*----------------------------------------------------------------------------------------------*/
103
43static void printInfo_Widget_(const iWidget *); 104static void printInfo_Widget_(const iWidget *);
44 105
45void releaseChildren_Widget(iWidget *d) { 106void releaseChildren_Widget(iWidget *d) {
@@ -66,6 +127,7 @@ void init_Widget(iWidget *d) {
66 d->children = NULL; 127 d->children = NULL;
67 d->parent = NULL; 128 d->parent = NULL;
68 d->commandHandler = NULL; 129 d->commandHandler = NULL;
130 d->drawBuf = NULL;
69 iZap(d->padding); 131 iZap(d->padding);
70} 132}
71 133
@@ -82,6 +144,7 @@ static void visualOffsetAnimation_Widget_(void *ptr) {
82 144
83void deinit_Widget(iWidget *d) { 145void deinit_Widget(iWidget *d) {
84 releaseChildren_Widget(d); 146 releaseChildren_Widget(d);
147 delete_WidgetDrawBuffer(d->drawBuf);
85#if 0 && !defined (NDEBUG) 148#if 0 && !defined (NDEBUG)
86 printf("widget %p (%s) deleted (on top:%d)\n", d, cstr_String(&d->id), 149 printf("widget %p (%s) deleted (on top:%d)\n", d, cstr_String(&d->id),
87 d->flags & keepOnTop_WidgetFlag ? 1 : 0); 150 d->flags & keepOnTop_WidgetFlag ? 1 : 0);
@@ -93,6 +156,16 @@ void deinit_Widget(iWidget *d) {
93 if (d->flags & visualOffset_WidgetFlag) { 156 if (d->flags & visualOffset_WidgetFlag) {
94 removeTicker_App(visualOffsetAnimation_Widget_, d); 157 removeTicker_App(visualOffsetAnimation_Widget_, d);
95 } 158 }
159 iWindow *win = get_Window();
160 if (win->lastHover == d) {
161 win->lastHover = NULL;
162 }
163 if (win->hover == d) {
164 win->hover = NULL;
165 }
166 if (d->flags & nativeMenu_WidgetFlag) {
167 releaseNativeMenu_Widget(d);
168 }
96 widgetDestroyed_Touch(d); 169 widgetDestroyed_Touch(d);
97} 170}
98 171
@@ -100,11 +173,15 @@ static void aboutToBeDestroyed_Widget_(iWidget *d) {
100 d->flags |= destroyPending_WidgetFlag; 173 d->flags |= destroyPending_WidgetFlag;
101 if (isFocused_Widget(d)) { 174 if (isFocused_Widget(d)) {
102 setFocus_Widget(NULL); 175 setFocus_Widget(NULL);
103 return; 176 //return; /* TODO: Why?! */
104 } 177 }
105 remove_Periodic(periodic_App(), d); 178 remove_Periodic(periodic_App(), d);
179 iWindow *win = get_Window();
106 if (isHover_Widget(d)) { 180 if (isHover_Widget(d)) {
107 get_Window()->hover = NULL; 181 win->hover = NULL;
182 }
183 if (win->lastHover == d) {
184 win->lastHover = NULL;
108 } 185 }
109 iForEach(ObjectList, i, d->children) { 186 iForEach(ObjectList, i, d->children) {
110 aboutToBeDestroyed_Widget_(as_Widget(i.object)); 187 aboutToBeDestroyed_Widget_(as_Widget(i.object));
@@ -151,6 +228,7 @@ void setFlags_Widget(iWidget *d, int64_t flags, iBool set) {
151 } 228 }
152 else { 229 else {
153 removeOne_PtrArray(onTop, d); 230 removeOne_PtrArray(onTop, d);
231 iAssert(indexOf_PtrArray(onTop, d) == iInvalidPos);
154 } 232 }
155 } 233 }
156 if (d->flags & arrangeWidth_WidgetFlag && 234 if (d->flags & arrangeWidth_WidgetFlag &&
@@ -196,6 +274,10 @@ iWidget *root_Widget(const iWidget *d) {
196 return d ? d->root->widget : NULL; 274 return d ? d->root->widget : NULL;
197} 275}
198 276
277iWindow *window_Widget(const iAnyObject *d) {
278 return constAs_Widget(d)->root->window;
279}
280
199void showCollapsed_Widget(iWidget *d, iBool show) { 281void showCollapsed_Widget(iWidget *d, iBool show) {
200 const iBool isVisible = !(d->flags & hidden_WidgetFlag); 282 const iBool isVisible = !(d->flags & hidden_WidgetFlag);
201 if ((isVisible && !show) || (!isVisible && show)) { 283 if ((isVisible && !show) || (!isVisible && show)) {
@@ -452,7 +534,7 @@ static void arrange_Widget_(iWidget *d) {
452 else if (d->flags & centerHorizontal_WidgetFlag) { 534 else if (d->flags & centerHorizontal_WidgetFlag) {
453 centerHorizontal_Widget_(d); 535 centerHorizontal_Widget_(d);
454 } 536 }
455 if (d->flags & resizeToParentWidth_WidgetFlag) { 537 if (d->flags & resizeToParentWidth_WidgetFlag && d->parent) {
456 iRect childBounds = zero_Rect(); 538 iRect childBounds = zero_Rect();
457 if (flags_Widget(d->parent) & arrangeWidth_WidgetFlag) { 539 if (flags_Widget(d->parent) & arrangeWidth_WidgetFlag) {
458 /* Can't go narrower than what the children require, though. */ 540 /* Can't go narrower than what the children require, though. */
@@ -462,7 +544,7 @@ static void arrange_Widget_(iWidget *d) {
462 setWidth_Widget_(d, iMaxi(width_Rect(innerRect_Widget_(d->parent)), 544 setWidth_Widget_(d, iMaxi(width_Rect(innerRect_Widget_(d->parent)),
463 width_Rect(childBounds))); 545 width_Rect(childBounds)));
464 } 546 }
465 if (d->flags & resizeToParentHeight_WidgetFlag) { 547 if (d->flags & resizeToParentHeight_WidgetFlag && d->parent) {
466 TRACE(d, "resize to parent height"); 548 TRACE(d, "resize to parent height");
467 setHeight_Widget_(d, height_Rect(innerRect_Widget_(d->parent))); 549 setHeight_Widget_(d, height_Rect(innerRect_Widget_(d->parent)));
468 } 550 }
@@ -817,9 +899,6 @@ iInt2 localToWindow_Widget(const iWidget *d, iInt2 localCoord) {
817 applyVisualOffset_Widget_(w, &pos); 899 applyVisualOffset_Widget_(w, &pos);
818 addv_I2(&window, pos); 900 addv_I2(&window, pos);
819 } 901 }
820#if defined (iPlatformMobile)
821 window.y += value_Anim(&get_Window()->rootOffset);
822#endif
823 return window; 902 return window;
824} 903}
825 904
@@ -899,15 +978,18 @@ static iBool filterEvent_Widget_(const iWidget *d, const SDL_Event *ev) {
899} 978}
900 979
901void unhover_Widget(void) { 980void unhover_Widget(void) {
902 get_Window()->hover = NULL; 981 iWidget **hover = &get_Window()->hover;
982 if (*hover) {
983 refresh_Widget(*hover);
984 }
985 *hover = NULL;
903} 986}
904 987
905iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { 988iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
906 //iAssert(d->root == get_Root());
907 if (!d->parent) { 989 if (!d->parent) {
908 if (get_Window()->focus && get_Window()->focus->root == d->root && isKeyboardEvent_(ev)) { 990 if (window_Widget(d)->focus && window_Widget(d)->focus->root == d->root && isKeyboardEvent_(ev)) {
909 /* Root dispatches keyboard events directly to the focused widget. */ 991 /* Root dispatches keyboard events directly to the focused widget. */
910 if (dispatchEvent_Widget(get_Window()->focus, ev)) { 992 if (dispatchEvent_Widget(window_Widget(d)->focus, ev)) {
911 return iTrue; 993 return iTrue;
912 } 994 }
913 } 995 }
@@ -936,7 +1018,8 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
936 } 1018 }
937 } 1019 }
938 else if (ev->type == SDL_MOUSEMOTION && 1020 else if (ev->type == SDL_MOUSEMOTION &&
939 (!get_Window()->hover || hasParent_Widget(d, get_Window()->hover)) && 1021 ev->motion.windowID == SDL_GetWindowID(window_Widget(d)->win) &&
1022 (!window_Widget(d)->hover || hasParent_Widget(d, window_Widget(d)->hover)) &&
940 flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag && 1023 flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag &&
941 ~flags_Widget(d) & disabled_WidgetFlag) { 1024 ~flags_Widget(d) & disabled_WidgetFlag) {
942 if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) { 1025 if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) {
@@ -955,11 +1038,11 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
955 iReverseForEach(ObjectList, i, d->children) { 1038 iReverseForEach(ObjectList, i, d->children) {
956 iWidget *child = as_Widget(i.object); 1039 iWidget *child = as_Widget(i.object);
957 //iAssert(child->root == d->root); 1040 //iAssert(child->root == d->root);
958 if (child == get_Window()->focus && isKeyboardEvent_(ev)) { 1041 if (child == window_Widget(d)->focus && isKeyboardEvent_(ev)) {
959 continue; /* Already dispatched. */ 1042 continue; /* Already dispatched. */
960 } 1043 }
961 if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) { 1044 if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) {
962 /* Already dispatched. */ 1045 /* Already dispatched. */
963 continue; 1046 continue;
964 } 1047 }
965 if (dispatchEvent_Widget(child, ev)) { 1048 if (dispatchEvent_Widget(child, ev)) {
@@ -974,7 +1057,7 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
974#endif 1057#endif
975#if 0 1058#if 0
976 if (ev->type == SDL_MOUSEMOTION) { 1059 if (ev->type == SDL_MOUSEMOTION) {
977 printf("[%p] %s:'%s' (on top) ate the motion\n", 1060 printf("[%p] %s:'%s' ate the motion\n",
978 child, class_Widget(child)->name, 1061 child, class_Widget(child)->name,
979 cstr_String(id_Widget(child))); 1062 cstr_String(id_Widget(child)));
980 fflush(stdout); 1063 fflush(stdout);
@@ -1008,24 +1091,60 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
1008 return iFalse; 1091 return iFalse;
1009} 1092}
1010 1093
1094void scrollInfo_Widget(const iWidget *d, iWidgetScrollInfo *info) {
1095 iRect bounds = boundsWithoutVisualOffset_Widget(d);
1096 const iRect winRect = adjusted_Rect(safeRect_Root(d->root),
1097 zero_I2(),
1098 init_I2(0, -get_MainWindow()->keyboardHeight));
1099 info->height = bounds.size.y;
1100 info->avail = height_Rect(winRect);
1101 if (info->avail >= info->height) {
1102 info->normScroll = 0.0f;
1103 info->thumbY = 0;
1104 info->thumbHeight = 0;
1105 }
1106 else {
1107 int scroll = top_Rect(winRect) - top_Rect(bounds);
1108 info->normScroll = scroll / (float) (info->height - info->avail);
1109 info->normScroll = iClamp(info->normScroll, 0.0f, 1.0f);
1110 info->thumbHeight = iMin(info->avail / 2, info->avail * info->avail / info->height);
1111 info->thumbY = top_Rect(winRect) + (info->avail - info->thumbHeight) * info->normScroll;
1112 }
1113}
1114
1011iBool scrollOverflow_Widget(iWidget *d, int delta) { 1115iBool scrollOverflow_Widget(iWidget *d, int delta) {
1012 iRect bounds = boundsWithoutVisualOffset_Widget(d); 1116 iRect bounds = boundsWithoutVisualOffset_Widget(d);
1013 const iInt2 rootSize = size_Root(d->root); 1117 const iRect winRect = adjusted_Rect(safeRect_Root(d->root),
1014 const iRect winRect = safeRect_Root(d->root); 1118 zero_I2(),
1015 const int yTop = top_Rect(winRect); 1119 init_I2(0, -get_MainWindow()->keyboardHeight));
1016 const int yBottom = bottom_Rect(winRect); 1120 const int yTop = top_Rect(winRect);
1121 const int yBottom = bottom_Rect(winRect);
1017 if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) { 1122 if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) {
1018 return iFalse; /* fits inside just fine */ 1123 return iFalse; /* fits inside just fine */
1019 } 1124 }
1020 //const int safeBottom = rootSize.y - yBottom; 1125 //const int safeBottom = rootSize.y - yBottom;
1021 bounds.pos.y += delta; 1126 iRangei validPosRange = { bottom_Rect(winRect) - height_Rect(bounds), yTop };
1022 const iRangei range = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; 1127 if (validPosRange.start > validPosRange.end) {
1128 validPosRange.start = validPosRange.end; /* no room to scroll */
1129 }
1130 if (delta) {
1131 if (delta < 0 && bounds.pos.y < validPosRange.start) {
1132 delta = 0;
1133 }
1134 if (delta > 0 && bounds.pos.y > validPosRange.end) {
1135 delta = 0;
1136 }
1137 bounds.pos.y += delta;
1138 if (delta < 0) {
1139 bounds.pos.y = iMax(bounds.pos.y, validPosRange.start);
1140 }
1141 else if (delta > 0) {
1142 bounds.pos.y = iMin(bounds.pos.y, validPosRange.end);
1143 }
1023// printf("range: %d ... %d\n", range.start, range.end); 1144// printf("range: %d ... %d\n", range.start, range.end);
1024 if (range.start >= range.end) {
1025 bounds.pos.y = range.end;
1026 } 1145 }
1027 else { 1146 else {
1028 bounds.pos.y = iClamp(bounds.pos.y, range.start, range.end); 1147 bounds.pos.y = iClamp(bounds.pos.y, validPosRange.start, validPosRange.end);
1029 } 1148 }
1030// if (delta >= 0) { 1149// if (delta >= 0) {
1031// bounds.pos.y = iMin(bounds.pos.y, yTop); 1150// bounds.pos.y = iMin(bounds.pos.y, yTop);
@@ -1036,7 +1155,8 @@ iBool scrollOverflow_Widget(iWidget *d, int delta) {
1036 const iInt2 newPos = windowToInner_Widget(d->parent, bounds.pos); 1155 const iInt2 newPos = windowToInner_Widget(d->parent, bounds.pos);
1037 if (!isEqual_I2(newPos, d->rect.pos)) { 1156 if (!isEqual_I2(newPos, d->rect.pos)) {
1038 d->rect.pos = newPos; 1157 d->rect.pos = newPos;
1039 refresh_Widget(d); 1158// refresh_Widget(d);
1159 postRefresh_App();
1040 } 1160 }
1041 return height_Rect(bounds) > height_Rect(winRect); 1161 return height_Rect(bounds) > height_Rect(winRect);
1042} 1162}
@@ -1077,6 +1197,9 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
1077 } 1197 }
1078 if (ev->user.code == command_UserEventCode) { 1198 if (ev->user.code == command_UserEventCode) {
1079 const char *cmd = command_UserEvent(ev); 1199 const char *cmd = command_UserEvent(ev);
1200 if (d->drawBuf && equal_Command(cmd, "theme.changed")) {
1201 d->drawBuf->isValid = iFalse;
1202 }
1080 if (d->flags & (leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag) && 1203 if (d->flags & (leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag) &&
1081 isVisible_Widget(d) && ~d->flags & disabled_WidgetFlag && 1204 isVisible_Widget(d) && ~d->flags & disabled_WidgetFlag &&
1082 equal_Command(cmd, "edgeswipe.moved")) { 1205 equal_Command(cmd, "edgeswipe.moved")) {
@@ -1130,7 +1253,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
1130 ev->button.x, 1253 ev->button.x,
1131 ev->button.y); 1254 ev->button.y);
1132 } 1255 }
1133 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); 1256 setCursor_Window(window_Widget(d), SDL_SYSTEM_CURSOR_ARROW);
1134 return iTrue; 1257 return iTrue;
1135 } 1258 }
1136 return iFalse; 1259 return iFalse;
@@ -1147,14 +1270,14 @@ int backgroundFadeColor_Widget(void) {
1147 } 1270 }
1148} 1271}
1149 1272
1150void drawBackground_Widget(const iWidget *d) { 1273iLocalDef iBool isDrawn_Widget_(const iWidget *d) {
1151 if (d->flags & noBackground_WidgetFlag) { 1274 return ~d->flags & hidden_WidgetFlag || d->flags & visualOffset_WidgetFlag;
1152 return; 1275}
1153 } 1276
1154 if (d->flags & hidden_WidgetFlag && ~d->flags & visualOffset_WidgetFlag) { 1277void drawLayerEffects_Widget(const iWidget *d) {
1155 return; 1278 /* Layered effects are not buffered, so they are drawn here separately. */
1156 } 1279 iAssert(isDrawn_Widget_(d));
1157 /* Popup menus have a shadowed border. */ 1280 iAssert(window_Widget(d) == get_Window());
1158 iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0; 1281 iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0;
1159 iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag; 1282 iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag;
1160 if (deviceType_App() == phone_AppDeviceType) { 1283 if (deviceType_App() == phone_AppDeviceType) {
@@ -1163,13 +1286,12 @@ void drawBackground_Widget(const iWidget *d) {
1163 shadowBorder = iFalse; 1286 shadowBorder = iFalse;
1164 } 1287 }
1165 } 1288 }
1289 const iBool isFaded = fadeBackground && ~d->flags & noFadeBackground_WidgetFlag;
1166 if (shadowBorder && ~d->flags & noShadowBorder_WidgetFlag) { 1290 if (shadowBorder && ~d->flags & noShadowBorder_WidgetFlag) {
1167 iPaint p; 1291 iPaint p;
1168 init_Paint(&p); 1292 init_Paint(&p);
1169 drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); 1293 drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30);
1170 } 1294 }
1171 const iBool isFaded = fadeBackground &&
1172 ~d->flags & noFadeBackground_WidgetFlag;
1173 if (isFaded) { 1295 if (isFaded) {
1174 iPaint p; 1296 iPaint p;
1175 init_Paint(&p); 1297 init_Paint(&p);
@@ -1183,15 +1305,68 @@ void drawBackground_Widget(const iWidget *d) {
1183 fillRect_Paint(&p, rect_Root(d->root), backgroundFadeColor_Widget()); 1305 fillRect_Paint(&p, rect_Root(d->root), backgroundFadeColor_Widget());
1184 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); 1306 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
1185 } 1307 }
1308#if defined (iPlatformAppleMobile)
1309 if (d->bgColor >= 0 && d->flags & (drawBackgroundToHorizontalSafeArea_WidgetFlag |
1310 drawBackgroundToVerticalSafeArea_WidgetFlag)) {
1311 iPaint p;
1312 init_Paint(&p);
1313 const iRect rect = bounds_Widget(d);
1314 const iInt2 rootSize = size_Root(d->root);
1315 const iInt2 center = divi_I2(rootSize, 2);
1316 int top = 0, right = 0, bottom = 0, left = 0;
1317 if (d->flags & drawBackgroundToHorizontalSafeArea_WidgetFlag) {
1318 const iBool isWide = width_Rect(rect) > rootSize.x * 9 / 10;
1319 if (isWide || mid_Rect(rect).x < center.x) {
1320 left = -left_Rect(rect);
1321 }
1322 if (isWide || mid_Rect(rect).x > center.x) {
1323 right = rootSize.x - right_Rect(rect);
1324 }
1325 }
1326 if (d->flags & drawBackgroundToVerticalSafeArea_WidgetFlag) {
1327 if (top_Rect(rect) > center.y) {
1328 bottom = rootSize.y - bottom_Rect(rect);
1329 }
1330 if (bottom_Rect(rect) < center.y) {
1331 top = -top_Rect(rect);
1332 }
1333 }
1334 if (top < 0) {
1335 fillRect_Paint(&p, (iRect){ init_I2(left_Rect(rect), 0),
1336 init_I2(width_Rect(rect), top_Rect(rect)) },
1337 d->bgColor);
1338 }
1339 if (left < 0) {
1340 fillRect_Paint(&p, (iRect){ init_I2(0, top_Rect(rect)),
1341 init_I2(left_Rect(rect), height_Rect(rect)) }, d->bgColor);
1342 }
1343 if (right > 0) {
1344 fillRect_Paint(&p, (iRect){ init_I2(right_Rect(rect), top_Rect(rect)),
1345 init_I2(right, height_Rect(rect)) }, d->bgColor);
1346 }
1347// adjustEdges_Rect(&rect, iMin(0, top), iMax(0, right), iMax(0, bottom), iMin(0, left));
1348 }
1349#endif
1350}
1351
1352void drawBackground_Widget(const iWidget *d) {
1353 if (d->flags & noBackground_WidgetFlag) {
1354 return;
1355 }
1356 if (!isDrawn_Widget_(d)) {
1357 return;
1358 }
1359 /* Popup menus have a shadowed border. */
1186 if (d->bgColor >= 0 || d->frameColor >= 0) { 1360 if (d->bgColor >= 0 || d->frameColor >= 0) {
1187 iRect rect = bounds_Widget(d); 1361 iRect rect = bounds_Widget(d);
1188 if (d->flags & drawBackgroundToBottom_WidgetFlag) { 1362 if (d->flags & drawBackgroundToBottom_WidgetFlag) {
1189 rect.size.y = size_Root(d->root).y - top_Rect(rect); 1363 rect.size.y += size_Root(d->root).y; // = iMax(rect.size.y, size_Root(d->root).y - top_Rect(rect));
1190 } 1364 }
1191 iPaint p; 1365 iPaint p;
1192 init_Paint(&p); 1366 init_Paint(&p);
1193 if (d->bgColor >= 0) { 1367 if (d->bgColor >= 0) {
1194#if defined (iPlatformAppleMobile) 1368#if 0 && defined (iPlatformAppleMobile)
1369 /* TODO: This is part of the unbuffered draw (layer effects). */
1195 if (d->flags & (drawBackgroundToHorizontalSafeArea_WidgetFlag | 1370 if (d->flags & (drawBackgroundToHorizontalSafeArea_WidgetFlag |
1196 drawBackgroundToVerticalSafeArea_WidgetFlag)) { 1371 drawBackgroundToVerticalSafeArea_WidgetFlag)) {
1197 const iInt2 rootSize = size_Root(d->root); 1372 const iInt2 rootSize = size_Root(d->root);
@@ -1214,7 +1389,7 @@ void drawBackground_Widget(const iWidget *d) {
1214 top = -top_Rect(rect); 1389 top = -top_Rect(rect);
1215 } 1390 }
1216 } 1391 }
1217 adjustEdges_Rect(&rect, top, right, bottom, left); 1392 adjustEdges_Rect(&rect, iMin(0, top), iMax(0, right), iMax(0, bottom), iMin(0, left));
1218 } 1393 }
1219#endif 1394#endif
1220 fillRect_Paint(&p, rect, d->bgColor); 1395 fillRect_Paint(&p, rect, d->bgColor);
@@ -1242,8 +1417,62 @@ void drawBackground_Widget(const iWidget *d) {
1242 } 1417 }
1243} 1418}
1244 1419
1245iLocalDef iBool isDrawn_Widget_(const iWidget *d) { 1420int drawCount_;
1246 return ~d->flags & hidden_WidgetFlag || d->flags & visualOffset_WidgetFlag; 1421
1422static iBool isRoot_Widget_(const iWidget *d) {
1423 return d == d->root->widget;
1424}
1425
1426iLocalDef iBool isFullyContainedByOther_Rect(const iRect d, const iRect other) {
1427 if (isEmpty_Rect(other)) {
1428 /* Nothing is contained by empty. */
1429 return iFalse;
1430 }
1431 if (isEmpty_Rect(d)) {
1432 /* Empty is fully contained by anything. */
1433 return iTrue;
1434 }
1435 return equal_Rect(intersect_Rect(d, other), d);
1436}
1437
1438static void addToPotentiallyVisible_Widget_(const iWidget *d, iPtrArray *pvs, iRect *fullyMasked) {
1439 if (isDrawn_Widget_(d)) {
1440 iRect bounds = bounds_Widget(d);
1441 if (d->flags & drawBackgroundToBottom_WidgetFlag) {
1442 bounds.size.y += size_Root(d->root).y; // iMax(bounds.size.y, size_Root(d->root).y - top_Rect(bounds));
1443 }
1444 if (isFullyContainedByOther_Rect(bounds, *fullyMasked)) {
1445 return; /* can't be seen */
1446 }
1447 pushBack_PtrArray(pvs, d);
1448 if (d->bgColor >= 0 && ~d->flags & noBackground_WidgetFlag &&
1449 isFullyContainedByOther_Rect(*fullyMasked, bounds)) {
1450 *fullyMasked = bounds;
1451 }
1452 }
1453}
1454
1455static void findPotentiallyVisible_Widget_(const iWidget *d, iPtrArray *pvs) {
1456 iRect fullyMasked = zero_Rect();
1457 if (isRoot_Widget_(d)) {
1458 iReverseConstForEach(PtrArray, i, onTop_Root(d->root)) {
1459 const iWidget *top = i.ptr;
1460 iAssert(top->parent);
1461 addToPotentiallyVisible_Widget_(top, pvs, &fullyMasked);
1462 }
1463 }
1464 iReverseConstForEach(ObjectList, i, d->children) {
1465 const iWidget *child = i.object;
1466 if (~child->flags & keepOnTop_WidgetFlag) {
1467 addToPotentiallyVisible_Widget_(child, pvs, &fullyMasked);
1468 }
1469 }
1470}
1471
1472iLocalDef void incrementDrawCount_(const iWidget *d) {
1473 if (class_Widget(d) != &Class_Widget || d->bgColor >= 0 || d->frameColor >= 0) {
1474 drawCount_++;
1475 }
1247} 1476}
1248 1477
1249void drawChildren_Widget(const iWidget *d) { 1478void drawChildren_Widget(const iWidget *d) {
@@ -1253,21 +1482,108 @@ void drawChildren_Widget(const iWidget *d) {
1253 iConstForEach(ObjectList, i, d->children) { 1482 iConstForEach(ObjectList, i, d->children) {
1254 const iWidget *child = constAs_Widget(i.object); 1483 const iWidget *child = constAs_Widget(i.object);
1255 if (~child->flags & keepOnTop_WidgetFlag && isDrawn_Widget_(child)) { 1484 if (~child->flags & keepOnTop_WidgetFlag && isDrawn_Widget_(child)) {
1485 incrementDrawCount_(child);
1256 class_Widget(child)->draw(child); 1486 class_Widget(child)->draw(child);
1257 } 1487 }
1258 } 1488 }
1489}
1490
1491void drawRoot_Widget(const iWidget *d) {
1492 iAssert(d == d->root->widget);
1259 /* Root draws the on-top widgets on top of everything else. */ 1493 /* Root draws the on-top widgets on top of everything else. */
1260 if (d == d->root->widget) { 1494 iPtrArray pvs;
1261 iConstForEach(PtrArray, i, onTop_Root(d->root)) { 1495 init_PtrArray(&pvs);
1262 const iWidget *top = *i.value; 1496 findPotentiallyVisible_Widget_(d, &pvs);
1263 class_Widget(top)->draw(top); 1497 iReverseConstForEach(PtrArray, i, &pvs) {
1264 } 1498 incrementDrawCount_(i.ptr);
1265 } 1499 class_Widget(i.ptr)->draw(i.ptr);
1500 }
1501 deinit_PtrArray(&pvs);
1502}
1503
1504void setDrawBufferEnabled_Widget(iWidget *d, iBool enable) {
1505 if (enable && !d->drawBuf) {
1506 d->drawBuf = new_WidgetDrawBuffer();
1507 }
1508 else if (!enable && d->drawBuf) {
1509 delete_WidgetDrawBuffer(d->drawBuf);
1510 d->drawBuf = NULL;
1511 }
1512}
1513
1514static void beginBufferDraw_Widget_(const iWidget *d) {
1515 if (d->drawBuf) {
1516// printf("[%p] drawbuffer update %d\n", d, d->drawBuf->isValid);
1517 if (d->drawBuf->isValid) {
1518 iAssert(!isEqual_I2(d->drawBuf->size, boundsForDraw_Widget_(d).size));
1519// printf(" drawBuf:%dx%d boundsForDraw:%dx%d\n",
1520// d->drawBuf->size.x, d->drawBuf->size.y,
1521// boundsForDraw_Widget_(d).size.x,
1522// boundsForDraw_Widget_(d).size.y);
1523 }
1524 const iRect bounds = bounds_Widget(d);
1525 SDL_Renderer *render = renderer_Window(get_Window());
1526 d->drawBuf->oldTarget = SDL_GetRenderTarget(render);
1527 d->drawBuf->oldOrigin = origin_Paint;
1528 realloc_WidgetDrawBuffer(d->drawBuf, render, boundsForDraw_Widget_(d).size);
1529 SDL_SetRenderTarget(render, d->drawBuf->texture);
1530// SDL_SetRenderDrawColor(render, 255, 0, 0, 128);
1531 SDL_SetRenderDrawColor(render, 0, 0, 0, 0);
1532 SDL_RenderClear(render);
1533 origin_Paint = neg_I2(bounds.pos); /* with current visual offset */
1534// printf("beginBufferDraw: origin %d,%d\n", origin_Paint.x, origin_Paint.y);
1535// fflush(stdout);
1536 }
1537}
1538
1539static void endBufferDraw_Widget_(const iWidget *d) {
1540 if (d->drawBuf) {
1541 d->drawBuf->isValid = iTrue;
1542 SDL_SetRenderTarget(renderer_Window(get_Window()), d->drawBuf->oldTarget);
1543 origin_Paint = d->drawBuf->oldOrigin;
1544// printf("endBufferDraw: origin %d,%d\n", origin_Paint.x, origin_Paint.y);
1545// fflush(stdout);
1546 }
1266} 1547}
1267 1548
1268void draw_Widget(const iWidget *d) { 1549void draw_Widget(const iWidget *d) {
1269 drawBackground_Widget(d); 1550 iAssert(window_Widget(d) == get_Window());
1270 drawChildren_Widget(d); 1551 if (!isDrawn_Widget_(d)) {
1552 if (d->drawBuf) {
1553// printf("[%p] drawBuffer released\n", d);
1554 release_WidgetDrawBuffer(d->drawBuf);
1555 }
1556 return;
1557 }
1558 drawLayerEffects_Widget(d);
1559 if (!d->drawBuf || !checkDrawBuffer_Widget_(d)) {
1560 beginBufferDraw_Widget_(d);
1561 drawBackground_Widget(d);
1562 drawChildren_Widget(d);
1563 endBufferDraw_Widget_(d);
1564 }
1565 if (d->drawBuf) {
1566 //iAssert(d->drawBuf->isValid);
1567 const iRect bounds = bounds_Widget(d);
1568 SDL_RenderCopy(renderer_Window(get_Window()), d->drawBuf->texture, NULL,
1569 &(SDL_Rect){ bounds.pos.x, bounds.pos.y,
1570 d->drawBuf->size.x, d->drawBuf->size.y });
1571 }
1572 if (d->flags & overflowScrollable_WidgetFlag) {
1573 iWidgetScrollInfo info;
1574 scrollInfo_Widget(d, &info);
1575 if (info.thumbHeight > 0) {
1576 iPaint p;
1577 init_Paint(&p);
1578 const int scrollWidth = gap_UI / 2;
1579 iRect bounds = bounds_Widget(d);
1580 bounds.pos.x = right_Rect(bounds) - scrollWidth * 3;
1581 bounds.size.x = scrollWidth;
1582 bounds.pos.y = info.thumbY;
1583 bounds.size.y = info.thumbHeight;
1584 fillRect_Paint(&p, bounds, tmQuote_ColorId);
1585 }
1586 }
1271} 1587}
1272 1588
1273iAny *addChild_Widget(iWidget *d, iAnyObject *child) { 1589iAny *addChild_Widget(iWidget *d, iAnyObject *child) {
@@ -1288,6 +1604,12 @@ iAny *addChildPosFlags_Widget(iWidget *d, iAnyObject *child, enum iWidgetAddPos
1288 d->children = new_ObjectList(); 1604 d->children = new_ObjectList();
1289 } 1605 }
1290 if (addPos == back_WidgetAddPos) { 1606 if (addPos == back_WidgetAddPos) {
1607 /* Remove a redundant border flags. */
1608 if (!isEmpty_ObjectList(d->children) &&
1609 as_Widget(back_ObjectList(d->children))->flags & borderBottom_WidgetFlag &&
1610 widget->flags & borderTop_WidgetFlag) {
1611 widget->flags &= ~borderTop_WidgetFlag;
1612 }
1291 pushBack_ObjectList(d->children, widget); /* ref */ 1613 pushBack_ObjectList(d->children, widget); /* ref */
1292 } 1614 }
1293 else { 1615 else {
@@ -1402,6 +1724,7 @@ iAny *hitChild_Widget(const iWidget *d, iInt2 coord) {
1402} 1724}
1403 1725
1404iAny *findChild_Widget(const iWidget *d, const char *id) { 1726iAny *findChild_Widget(const iWidget *d, const char *id) {
1727 if (!d) return NULL;
1405 if (cmp_String(id_Widget(d), id) == 0) { 1728 if (cmp_String(id_Widget(d), id) == 0) {
1406 return iConstCast(iAny *, d); 1729 return iConstCast(iAny *, d);
1407 } 1730 }
@@ -1506,7 +1829,17 @@ iBool equalWidget_Command(const char *cmd, const iWidget *widget, const char *ch
1506 if (equal_Command(cmd, checkCommand)) { 1829 if (equal_Command(cmd, checkCommand)) {
1507 const iWidget *src = pointer_Command(cmd); 1830 const iWidget *src = pointer_Command(cmd);
1508 iAssert(!src || strstr(cmd, " ptr:")); 1831 iAssert(!src || strstr(cmd, " ptr:"));
1509 return src == widget || hasParent_Widget(src, widget); 1832 if (src == widget || hasParent_Widget(src, widget)) {
1833 return iTrue;
1834 }
1835// if (src && type_Window(window_Widget(src)) == popup_WindowType) {
1836// /* Special case: command was emitted from a popup widget. The popup root widget actually
1837// belongs to someone else. */
1838// iWidget *realParent = userData_Object(src->root->widget);
1839// iAssert(realParent);
1840// iAssert(isInstance_Object(realParent, &Class_Widget));
1841// return realParent == widget || hasParent_Widget(realParent, widget);
1842// }
1510 } 1843 }
1511 return iFalse; 1844 return iFalse;
1512} 1845}
@@ -1557,7 +1890,9 @@ iWidget *focus_Widget(void) {
1557} 1890}
1558 1891
1559void setHover_Widget(iWidget *d) { 1892void setHover_Widget(iWidget *d) {
1560 get_Window()->hover = d; 1893 iWindow *win = get_Window();
1894 iAssert(win);
1895 win->hover = d;
1561} 1896}
1562 1897
1563iWidget *hover_Widget(void) { 1898iWidget *hover_Widget(void) {
@@ -1646,6 +1981,10 @@ void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) {
1646 } 1981 }
1647 if (!isGlobal) { 1982 if (!isGlobal) {
1648 iAssert(isInstance_Object(d, &Class_Widget)); 1983 iAssert(isInstance_Object(d, &Class_Widget));
1984 if (type_Window(window_Widget(d)) == popup_WindowType) {
1985 postCommandf_Root(((const iWidget *) d)->root, "cancel popup:1 ptr:%p", d);
1986 d = userData_Object(root_Widget(d));
1987 }
1649 appendFormat_String(&str, " ptr:%p", d); 1988 appendFormat_String(&str, " ptr:%p", d);
1650 } 1989 }
1651 postCommandString_Root(((const iWidget *) d)->root, &str); 1990 postCommandString_Root(((const iWidget *) d)->root, &str);
@@ -1653,11 +1992,20 @@ void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) {
1653} 1992}
1654 1993
1655void refresh_Widget(const iAnyObject *d) { 1994void refresh_Widget(const iAnyObject *d) {
1995 if (!d) return;
1656 /* TODO: Could be widget specific, if parts of the tree are cached. */ 1996 /* TODO: Could be widget specific, if parts of the tree are cached. */
1657 /* TODO: The visbuffer in DocumentWidget and ListWidget could be moved to be a general 1997 /* TODO: The visbuffer in DocumentWidget and ListWidget could be moved to be a general
1658 purpose feature of Widget. */ 1998 purpose feature of Widget. */
1659 iAssert(isInstance_Object(d, &Class_Widget)); 1999 iAssert(isInstance_Object(d, &Class_Widget));
1660 iUnused(d); 2000 /* Mark draw buffers invalid. */
2001 for (const iWidget *w = d; w; w = w->parent) {
2002 if (w->drawBuf) {
2003// if (w->drawBuf->isValid) {
2004// printf("[%p] drawbuffer invalidated by %p\n", w, d); fflush(stdout);
2005// }
2006 w->drawBuf->isValid = iFalse;
2007 }
2008 }
1661 postRefresh_App(); 2009 postRefresh_App();
1662} 2010}
1663 2011