diff options
Diffstat (limited to 'src/ui/widget.c')
-rw-r--r-- | src/ui/widget.c | 454 |
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 | ||
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 = iMax(bounds.size.y, size_Root(d->root).y); | ||
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); |
@@ -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 | ||
277 | iWindow *window_Widget(const iAnyObject *d) { | ||
278 | return constAs_Widget(d)->root->window; | ||
279 | } | ||
280 | |||
199 | void showCollapsed_Widget(iWidget *d, iBool show) { | 281 | void 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 | ||
901 | void unhover_Widget(void) { | 980 | void 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 | ||
905 | iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { | 988 | iBool 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 | ||
1094 | void 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 | |||
1011 | iBool scrollOverflow_Widget(iWidget *d, int delta) { | 1115 | iBool 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 | ||
1150 | void drawBackground_Widget(const iWidget *d) { | 1273 | iLocalDef 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) { | 1277 | void 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 | |||
1352 | void 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 | ||
1245 | iLocalDef iBool isDrawn_Widget_(const iWidget *d) { | 1420 | int drawCount_; |
1246 | return ~d->flags & hidden_WidgetFlag || d->flags & visualOffset_WidgetFlag; | 1421 | |
1422 | static iBool isRoot_Widget_(const iWidget *d) { | ||
1423 | return d == d->root->widget; | ||
1424 | } | ||
1425 | |||
1426 | iLocalDef 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 | |||
1438 | static 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 | |||
1455 | static 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 | |||
1472 | iLocalDef 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 | ||
1249 | void drawChildren_Widget(const iWidget *d) { | 1478 | void 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 | |||
1491 | void 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 | |||
1504 | void 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 | |||
1514 | static 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 | |||
1539 | static 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 | ||
1268 | void draw_Widget(const iWidget *d) { | 1549 | void 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 | ||
1273 | iAny *addChild_Widget(iWidget *d, iAnyObject *child) { | 1589 | iAny *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 | ||
1404 | iAny *findChild_Widget(const iWidget *d, const char *id) { | 1726 | iAny *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 | ||
1559 | void setHover_Widget(iWidget *d) { | 1892 | void 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 | ||
1563 | iWidget *hover_Widget(void) { | 1898 | iWidget *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 | ||
1655 | void refresh_Widget(const iAnyObject *d) { | 1994 | void 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 | ||