diff options
Diffstat (limited to 'src/ui/widget.c')
-rw-r--r-- | src/ui/widget.c | 241 |
1 files changed, 217 insertions, 24 deletions
diff --git a/src/ui/widget.c b/src/ui/widget.c index 659a00cc..66cd0e7b 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 | ||
43 | struct Impl_WidgetDrawBuffer { | ||
44 | SDL_Texture *texture; | ||
45 | iInt2 size; | ||
46 | iBool isValid; | ||
47 | SDL_Texture *oldTarget; | ||
48 | iInt2 oldOrigin; | ||
49 | }; | ||
50 | |||
51 | static void init_WidgetDrawBuffer(iWidgetDrawBuffer *d) { | ||
52 | d->texture = NULL; | ||
53 | d->size = zero_I2(); | ||
54 | d->isValid = iFalse; | ||
55 | d->oldTarget = NULL; | ||
56 | } | ||
57 | |||
58 | static void deinit_WidgetDrawBuffer(iWidgetDrawBuffer *d) { | ||
59 | SDL_DestroyTexture(d->texture); | ||
60 | } | ||
61 | |||
62 | iDefineTypeConstruction(WidgetDrawBuffer) | ||
63 | |||
64 | static 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 | |||
80 | static 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 | |||
89 | static iRect boundsForDraw_Widget_(const iWidget *d) { | ||
90 | iRect bounds = bounds_Widget(d); | ||
91 | if (d->flags & drawBackgroundToBottom_WidgetFlag) { | ||
92 | bounds.size.y = iMaxi(bounds.size.y, size_Root(d->root).y - top_Rect(bounds)); | ||
93 | } | ||
94 | return bounds; | ||
95 | } | ||
96 | |||
97 | static 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 | |||
43 | static void printInfo_Widget_(const iWidget *); | 104 | static void printInfo_Widget_(const iWidget *); |
44 | 105 | ||
45 | void releaseChildren_Widget(iWidget *d) { | 106 | void 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 | ||
83 | void deinit_Widget(iWidget *d) { | 145 | void 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); |
@@ -1036,7 +1099,8 @@ iBool scrollOverflow_Widget(iWidget *d, int delta) { | |||
1036 | const iInt2 newPos = windowToInner_Widget(d->parent, bounds.pos); | 1099 | const iInt2 newPos = windowToInner_Widget(d->parent, bounds.pos); |
1037 | if (!isEqual_I2(newPos, d->rect.pos)) { | 1100 | if (!isEqual_I2(newPos, d->rect.pos)) { |
1038 | d->rect.pos = newPos; | 1101 | d->rect.pos = newPos; |
1039 | refresh_Widget(d); | 1102 | // refresh_Widget(d); |
1103 | postRefresh_App(); | ||
1040 | } | 1104 | } |
1041 | return height_Rect(bounds) > height_Rect(winRect); | 1105 | return height_Rect(bounds) > height_Rect(winRect); |
1042 | } | 1106 | } |
@@ -1077,6 +1141,9 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
1077 | } | 1141 | } |
1078 | if (ev->user.code == command_UserEventCode) { | 1142 | if (ev->user.code == command_UserEventCode) { |
1079 | const char *cmd = command_UserEvent(ev); | 1143 | const char *cmd = command_UserEvent(ev); |
1144 | if (d->drawBuf && equal_Command(cmd, "theme.changed")) { | ||
1145 | d->drawBuf->isValid = iFalse; | ||
1146 | } | ||
1080 | if (d->flags & (leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag) && | 1147 | if (d->flags & (leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag) && |
1081 | isVisible_Widget(d) && ~d->flags & disabled_WidgetFlag && | 1148 | isVisible_Widget(d) && ~d->flags & disabled_WidgetFlag && |
1082 | equal_Command(cmd, "edgeswipe.moved")) { | 1149 | equal_Command(cmd, "edgeswipe.moved")) { |
@@ -1147,14 +1214,13 @@ int backgroundFadeColor_Widget(void) { | |||
1147 | } | 1214 | } |
1148 | } | 1215 | } |
1149 | 1216 | ||
1150 | void drawBackground_Widget(const iWidget *d) { | 1217 | iLocalDef iBool isDrawn_Widget_(const iWidget *d) { |
1151 | if (d->flags & noBackground_WidgetFlag) { | 1218 | return ~d->flags & hidden_WidgetFlag || d->flags & visualOffset_WidgetFlag; |
1152 | return; | 1219 | } |
1153 | } | 1220 | |
1154 | if (d->flags & hidden_WidgetFlag && ~d->flags & visualOffset_WidgetFlag) { | 1221 | static void drawLayerEffects_Widget_(const iWidget *d) { |
1155 | return; | 1222 | /* Layered effects are not buffered, so they are drawn here separately. */ |
1156 | } | 1223 | iAssert(isDrawn_Widget_(d)); |
1157 | /* Popup menus have a shadowed border. */ | ||
1158 | iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0; | 1224 | iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0; |
1159 | iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag; | 1225 | iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag; |
1160 | if (deviceType_App() == phone_AppDeviceType) { | 1226 | if (deviceType_App() == phone_AppDeviceType) { |
@@ -1163,13 +1229,12 @@ void drawBackground_Widget(const iWidget *d) { | |||
1163 | shadowBorder = iFalse; | 1229 | shadowBorder = iFalse; |
1164 | } | 1230 | } |
1165 | } | 1231 | } |
1232 | const iBool isFaded = fadeBackground && ~d->flags & noFadeBackground_WidgetFlag; | ||
1166 | if (shadowBorder && ~d->flags & noShadowBorder_WidgetFlag) { | 1233 | if (shadowBorder && ~d->flags & noShadowBorder_WidgetFlag) { |
1167 | iPaint p; | 1234 | iPaint p; |
1168 | init_Paint(&p); | 1235 | init_Paint(&p); |
1169 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); | 1236 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); |
1170 | } | 1237 | } |
1171 | const iBool isFaded = fadeBackground && | ||
1172 | ~d->flags & noFadeBackground_WidgetFlag; | ||
1173 | if (isFaded) { | 1238 | if (isFaded) { |
1174 | iPaint p; | 1239 | iPaint p; |
1175 | init_Paint(&p); | 1240 | init_Paint(&p); |
@@ -1183,10 +1248,20 @@ void drawBackground_Widget(const iWidget *d) { | |||
1183 | fillRect_Paint(&p, rect_Root(d->root), backgroundFadeColor_Widget()); | 1248 | fillRect_Paint(&p, rect_Root(d->root), backgroundFadeColor_Widget()); |
1184 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | 1249 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); |
1185 | } | 1250 | } |
1251 | } | ||
1252 | |||
1253 | void drawBackground_Widget(const iWidget *d) { | ||
1254 | if (d->flags & noBackground_WidgetFlag) { | ||
1255 | return; | ||
1256 | } | ||
1257 | if (!isDrawn_Widget_(d)) { | ||
1258 | return; | ||
1259 | } | ||
1260 | /* Popup menus have a shadowed border. */ | ||
1186 | if (d->bgColor >= 0 || d->frameColor >= 0) { | 1261 | if (d->bgColor >= 0 || d->frameColor >= 0) { |
1187 | iRect rect = bounds_Widget(d); | 1262 | iRect rect = bounds_Widget(d); |
1188 | if (d->flags & drawBackgroundToBottom_WidgetFlag) { | 1263 | if (d->flags & drawBackgroundToBottom_WidgetFlag) { |
1189 | rect.size.y = size_Root(d->root).y - top_Rect(rect); | 1264 | rect.size.y = iMax(rect.size.y, size_Root(d->root).y - top_Rect(rect)); |
1190 | } | 1265 | } |
1191 | iPaint p; | 1266 | iPaint p; |
1192 | init_Paint(&p); | 1267 | init_Paint(&p); |
@@ -1242,8 +1317,54 @@ void drawBackground_Widget(const iWidget *d) { | |||
1242 | } | 1317 | } |
1243 | } | 1318 | } |
1244 | 1319 | ||
1245 | iLocalDef iBool isDrawn_Widget_(const iWidget *d) { | 1320 | int drawCount_; |
1246 | return ~d->flags & hidden_WidgetFlag || d->flags & visualOffset_WidgetFlag; | 1321 | |
1322 | static iBool isRoot_Widget_(const iWidget *d) { | ||
1323 | return d == d->root->widget; | ||
1324 | } | ||
1325 | |||
1326 | iLocalDef iBool isFullyContainedByOther_Rect(const iRect d, const iRect other) { | ||
1327 | if (isEmpty_Rect(other)) { | ||
1328 | /* Nothing is contained by empty. */ | ||
1329 | return iFalse; | ||
1330 | } | ||
1331 | if (isEmpty_Rect(d)) { | ||
1332 | /* Empty is fully contained by anything. */ | ||
1333 | return iTrue; | ||
1334 | } | ||
1335 | return equal_Rect(intersect_Rect(d, other), d); | ||
1336 | } | ||
1337 | |||
1338 | static void addToPotentiallyVisible_Widget_(const iWidget *d, iPtrArray *pvs, iRect *fullyMasked) { | ||
1339 | if (isDrawn_Widget_(d)) { | ||
1340 | iRect bounds = bounds_Widget(d); | ||
1341 | if (d->flags & drawBackgroundToBottom_WidgetFlag) { | ||
1342 | bounds.size.y = size_Root(d->root).y - top_Rect(bounds); | ||
1343 | } | ||
1344 | if (isFullyContainedByOther_Rect(bounds, *fullyMasked)) { | ||
1345 | return; /* can't be seen */ | ||
1346 | } | ||
1347 | pushBack_PtrArray(pvs, d); | ||
1348 | if (d->bgColor >= 0 && ~d->flags & noBackground_WidgetFlag && | ||
1349 | isFullyContainedByOther_Rect(*fullyMasked, bounds)) { | ||
1350 | *fullyMasked = bounds; | ||
1351 | } | ||
1352 | } | ||
1353 | } | ||
1354 | |||
1355 | static void findPotentiallyVisible_Widget_(const iWidget *d, iPtrArray *pvs) { | ||
1356 | iRect fullyMasked = zero_Rect(); | ||
1357 | if (isRoot_Widget_(d)) { | ||
1358 | iReverseConstForEach(PtrArray, i, onTop_Root(d->root)) { | ||
1359 | addToPotentiallyVisible_Widget_(i.ptr, pvs, &fullyMasked); | ||
1360 | } | ||
1361 | } | ||
1362 | iReverseConstForEach(ObjectList, i, d->children) { | ||
1363 | const iWidget *child = i.object; | ||
1364 | if (~child->flags & keepOnTop_WidgetFlag) { | ||
1365 | addToPotentiallyVisible_Widget_(child, pvs, &fullyMasked); | ||
1366 | } | ||
1367 | } | ||
1247 | } | 1368 | } |
1248 | 1369 | ||
1249 | void drawChildren_Widget(const iWidget *d) { | 1370 | void drawChildren_Widget(const iWidget *d) { |
@@ -1253,21 +1374,85 @@ void drawChildren_Widget(const iWidget *d) { | |||
1253 | iConstForEach(ObjectList, i, d->children) { | 1374 | iConstForEach(ObjectList, i, d->children) { |
1254 | const iWidget *child = constAs_Widget(i.object); | 1375 | const iWidget *child = constAs_Widget(i.object); |
1255 | if (~child->flags & keepOnTop_WidgetFlag && isDrawn_Widget_(child)) { | 1376 | if (~child->flags & keepOnTop_WidgetFlag && isDrawn_Widget_(child)) { |
1377 | drawCount_++; | ||
1256 | class_Widget(child)->draw(child); | 1378 | class_Widget(child)->draw(child); |
1257 | } | 1379 | } |
1258 | } | 1380 | } |
1381 | } | ||
1382 | |||
1383 | void drawRoot_Widget(const iWidget *d) { | ||
1384 | iAssert(d == d->root->widget); | ||
1259 | /* Root draws the on-top widgets on top of everything else. */ | 1385 | /* Root draws the on-top widgets on top of everything else. */ |
1260 | if (d == d->root->widget) { | 1386 | iPtrArray pvs; |
1261 | iConstForEach(PtrArray, i, onTop_Root(d->root)) { | 1387 | init_PtrArray(&pvs); |
1262 | const iWidget *top = *i.value; | 1388 | findPotentiallyVisible_Widget_(d, &pvs); |
1263 | class_Widget(top)->draw(top); | 1389 | iReverseConstForEach(PtrArray, i, &pvs) { |
1264 | } | 1390 | drawCount_++; |
1265 | } | 1391 | class_Widget(i.ptr)->draw(i.ptr); |
1392 | } | ||
1393 | deinit_PtrArray(&pvs); | ||
1394 | } | ||
1395 | |||
1396 | void setDrawBufferEnabled_Widget(iWidget *d, iBool enable) { | ||
1397 | if (enable && !d->drawBuf) { | ||
1398 | d->drawBuf = new_WidgetDrawBuffer(); | ||
1399 | } | ||
1400 | else if (!enable && d->drawBuf) { | ||
1401 | delete_WidgetDrawBuffer(d->drawBuf); | ||
1402 | d->drawBuf = NULL; | ||
1403 | } | ||
1404 | } | ||
1405 | |||
1406 | static void beginBufferDraw_Widget_(const iWidget *d) { | ||
1407 | if (d->drawBuf) { | ||
1408 | // printf("[%p] drawbuffer update %d\n", d, d->drawBuf->isValid); | ||
1409 | const iRect bounds = bounds_Widget(d); | ||
1410 | SDL_Renderer *render = renderer_Window(get_Window()); | ||
1411 | d->drawBuf->oldTarget = SDL_GetRenderTarget(render); | ||
1412 | d->drawBuf->oldOrigin = origin_Paint; | ||
1413 | realloc_WidgetDrawBuffer(d->drawBuf, render, boundsForDraw_Widget_(d).size); | ||
1414 | SDL_SetRenderTarget(render, d->drawBuf->texture); | ||
1415 | //SDL_SetRenderDrawColor(render, 255, 0, 0, 128); | ||
1416 | SDL_SetRenderDrawColor(render, 0, 0, 0, 0); | ||
1417 | SDL_RenderClear(render); | ||
1418 | origin_Paint = neg_I2(bounds.pos); /* with current visual offset */ | ||
1419 | // printf("beginBufferDraw: origin %d,%d\n", origin_Paint.x, origin_Paint.y); | ||
1420 | // fflush(stdout); | ||
1421 | } | ||
1422 | } | ||
1423 | |||
1424 | static void endBufferDraw_Widget_(const iWidget *d) { | ||
1425 | if (d->drawBuf) { | ||
1426 | d->drawBuf->isValid = iTrue; | ||
1427 | SDL_SetRenderTarget(renderer_Window(get_Window()), d->drawBuf->oldTarget); | ||
1428 | origin_Paint = d->drawBuf->oldOrigin; | ||
1429 | // printf("endBufferDraw: origin %d,%d\n", origin_Paint.x, origin_Paint.y); | ||
1430 | // fflush(stdout); | ||
1431 | } | ||
1266 | } | 1432 | } |
1267 | 1433 | ||
1268 | void draw_Widget(const iWidget *d) { | 1434 | void draw_Widget(const iWidget *d) { |
1269 | drawBackground_Widget(d); | 1435 | if (!isDrawn_Widget_(d)) { |
1270 | drawChildren_Widget(d); | 1436 | if (d->drawBuf) { |
1437 | // printf("[%p] drawBuffer released\n", d); | ||
1438 | release_WidgetDrawBuffer(d->drawBuf); | ||
1439 | } | ||
1440 | return; | ||
1441 | } | ||
1442 | drawLayerEffects_Widget_(d); | ||
1443 | if (!d->drawBuf || !checkDrawBuffer_Widget_(d)) { | ||
1444 | beginBufferDraw_Widget_(d); | ||
1445 | drawBackground_Widget(d); | ||
1446 | drawChildren_Widget(d); | ||
1447 | endBufferDraw_Widget_(d); | ||
1448 | } | ||
1449 | if (d->drawBuf) { | ||
1450 | iAssert(d->drawBuf->isValid); | ||
1451 | const iRect bounds = bounds_Widget(d); | ||
1452 | SDL_RenderCopy(renderer_Window(get_Window()), d->drawBuf->texture, NULL, | ||
1453 | &(SDL_Rect){ bounds.pos.x, bounds.pos.y, | ||
1454 | d->drawBuf->size.x, d->drawBuf->size.y }); | ||
1455 | } | ||
1271 | } | 1456 | } |
1272 | 1457 | ||
1273 | iAny *addChild_Widget(iWidget *d, iAnyObject *child) { | 1458 | iAny *addChild_Widget(iWidget *d, iAnyObject *child) { |
@@ -1659,12 +1844,20 @@ void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) { | |||
1659 | deinit_String(&str); | 1844 | deinit_String(&str); |
1660 | } | 1845 | } |
1661 | 1846 | ||
1662 | void refresh_Widget(const iAnyObject *d) { | 1847 | void refresh_Widget(const iAnyObject *d) { |
1663 | /* TODO: Could be widget specific, if parts of the tree are cached. */ | 1848 | /* TODO: Could be widget specific, if parts of the tree are cached. */ |
1664 | /* TODO: The visbuffer in DocumentWidget and ListWidget could be moved to be a general | 1849 | /* TODO: The visbuffer in DocumentWidget and ListWidget could be moved to be a general |
1665 | purpose feature of Widget. */ | 1850 | purpose feature of Widget. */ |
1666 | iAssert(isInstance_Object(d, &Class_Widget)); | 1851 | iAssert(isInstance_Object(d, &Class_Widget)); |
1667 | iUnused(d); | 1852 | /* Mark draw buffers invalid. */ |
1853 | for (const iWidget *w = d; w; w = w->parent) { | ||
1854 | if (w->drawBuf) { | ||
1855 | // if (w->drawBuf->isValid) { | ||
1856 | // printf("[%p] drawbuffer invalidated by %p\n", w, d); fflush(stdout); | ||
1857 | // } | ||
1858 | w->drawBuf->isValid = iFalse; | ||
1859 | } | ||
1860 | } | ||
1668 | postRefresh_App(); | 1861 | postRefresh_App(); |
1669 | } | 1862 | } |
1670 | 1863 | ||