summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-05-14 07:24:32 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-05-14 07:24:32 +0300
commitfe59111e9671fba8d1c64def9831716860ea5e44 (patch)
treefdb44222af394b4924ade2715205a813f1db2f1b
parent713dcca5da21897fd8f5d7519112429273d7233e (diff)
InputWidget: Fixed cursor moving; scroll the dialog
Up/down movement sometimes ended up in the wrong cursor position. Now the nearest overflow-scrollable parent scrolls to keep the cursor visible.
-rw-r--r--src/ui/inputwidget.c123
-rw-r--r--src/ui/text.c4
-rw-r--r--src/ui/touch.c15
-rw-r--r--src/ui/widget.c22
-rw-r--r--src/ui/widget.h2
5 files changed, 107 insertions, 59 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 51447f21..6639f957 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -42,8 +42,12 @@ static const size_t maxUndo_InputWidget_ = 64;
42 42
43static void enableEditorKeysInMenus_(iBool enable) { 43static void enableEditorKeysInMenus_(iBool enable) {
44#if defined (iPlatformAppleDesktop) 44#if defined (iPlatformAppleDesktop)
45 enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable); 45 enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable);
46 enableMenuItemsByKey_MacOS(SDLK_RIGHT, KMOD_PRIMARY, enable); 46 enableMenuItemsByKey_MacOS(SDLK_RIGHT, KMOD_PRIMARY, enable);
47 enableMenuItemsByKey_MacOS(SDLK_UP, KMOD_PRIMARY, enable);
48 enableMenuItemsByKey_MacOS(SDLK_DOWN, KMOD_PRIMARY, enable);
49 enableMenuItemsByKey_MacOS(SDLK_UP, KMOD_PRIMARY | KMOD_SHIFT, enable);
50 enableMenuItemsByKey_MacOS(SDLK_DOWN, KMOD_PRIMARY | KMOD_SHIFT, enable);
47#else 51#else
48 iUnused(enable); 52 iUnused(enable);
49#endif 53#endif
@@ -84,6 +88,7 @@ iDeclareType(InputLine)
84 88
85struct Impl_InputLine { 89struct Impl_InputLine {
86 size_t offset; /* character position from the beginning */ 90 size_t offset; /* character position from the beginning */
91 size_t len; /* length as characters */
87 iString text; /* UTF-8 */ 92 iString text; /* UTF-8 */
88}; 93};
89 94
@@ -138,7 +143,23 @@ static void clearUndo_InputWidget_(iInputWidget *d) {
138 clear_Array(&d->undoStack); 143 clear_Array(&d->undoStack);
139} 144}
140 145
146iLocalDef iInt2 padding_(void) {
147 return init_I2(gap_UI / 2, gap_UI / 2);
148}
149
150static iRect contentBounds_InputWidget_(const iInputWidget *d) {
151 const iWidget *w = constAs_Widget(d);
152 // const iRect widgetBounds = bounds_Widget(w);
153 iRect bounds = adjusted_Rect(bounds_Widget(w),
154 addX_I2(padding_(), d->leftPadding),
155 neg_I2(addX_I2(padding_(), d->rightPadding)));
156 shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0));
157 bounds.pos.y += padding_().y / 2;
158 return bounds;
159}
160
141static void updateCursorLine_InputWidget_(iInputWidget *d) { 161static void updateCursorLine_InputWidget_(iInputWidget *d) {
162 iWidget *w = as_Widget(d);
142 d->cursorLine = 0; 163 d->cursorLine = 0;
143 iConstForEach(Array, i, &d->lines) { 164 iConstForEach(Array, i, &d->lines) {
144 const iInputLine *line = i.value; 165 const iInputLine *line = i.value;
@@ -147,6 +168,20 @@ static void updateCursorLine_InputWidget_(iInputWidget *d) {
147 } 168 }
148 d->cursorLine = index_ArrayConstIterator(&i); 169 d->cursorLine = index_ArrayConstIterator(&i);
149 } 170 }
171 /* May need to scroll to keep the cursor visible. */
172 iWidget *flow = findOverflowScrollable_Widget(w);
173 if (flow) {
174 const iRect rootRect = rect_Root(w->root);
175 int yCursor = contentBounds_InputWidget_(d).pos.y +
176 lineHeight_Text(d->font) * d->cursorLine;
177 const int margin = lineHeight_Text(d->font) * 3;
178 if (yCursor < top_Rect(rootRect) + margin) {
179 scrollOverflow_Widget(flow, top_Rect(rootRect) + margin - yCursor);
180 }
181 else if (yCursor > bottom_Rect(rootRect) - margin * 3 / 2) {
182 scrollOverflow_Widget(flow, bottom_Rect(rootRect) - margin * 3 / 2 - yCursor);
183 }
184 }
150} 185}
151 186
152static void showCursor_InputWidget_(iInputWidget *d) { 187static void showCursor_InputWidget_(iInputWidget *d) {
@@ -161,21 +196,6 @@ static void invalidateBuffered_InputWidget_(iInputWidget *d) {
161 } 196 }
162} 197}
163 198
164iLocalDef iInt2 padding_(void) {
165 return init_I2(gap_UI / 2, gap_UI / 2);
166}
167
168static iRect contentBounds_InputWidget_(const iInputWidget *d) {
169 const iWidget *w = constAs_Widget(d);
170 const iRect widgetBounds = bounds_Widget(w);
171 iRect bounds = adjusted_Rect(bounds_Widget(w),
172 addX_I2(padding_(), d->leftPadding),
173 neg_I2(addX_I2(padding_(), d->rightPadding)));
174 shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0));
175 bounds.pos.y += padding_().y / 2;
176 return bounds;
177}
178
179static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { 199static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) {
180 if (d->maxLen) { 200 if (d->maxLen) {
181 /* Set a fixed size based on maximum possible width of the text. */ 201 /* Set a fixed size based on maximum possible width of the text. */
@@ -226,6 +246,7 @@ static void updateLines_InputWidget_(iInputWidget *d) {
226 init_InputLine(&line); 246 init_InputLine(&line);
227 iString *u8 = visText_InputWidget_(d); 247 iString *u8 = visText_InputWidget_(d);
228 set_String(&line.text, u8); 248 set_String(&line.text, u8);
249 line.len = length_String(u8);
229 delete_String(u8); 250 delete_String(u8);
230 pushBack_Array(&d->lines, &line); 251 pushBack_Array(&d->lines, &line);
231 updateCursorLine_InputWidget_(d); 252 updateCursorLine_InputWidget_(d);
@@ -249,14 +270,16 @@ static void updateLines_InputWidget_(iInputWidget *d) {
249 init_InputLine(&line); 270 init_InputLine(&line);
250 setRange_String(&line.text, part); 271 setRange_String(&line.text, part);
251 line.offset = charPos; 272 line.offset = charPos;
273 line.len = length_String(&line.text);
252 pushBack_Array(&d->lines, &line); 274 pushBack_Array(&d->lines, &line);
253 charPos += length_String(&line.text); 275 charPos += line.len;
254 content.start = endPos; 276 content.start = endPos;
255 } 277 }
256 if (isEmpty_Array(&d->lines) || endsWith_String(u8, "\n")) { 278 if (isEmpty_Array(&d->lines) || endsWith_String(u8, "\n")) {
257 /* Always at least one empty line. */ 279 /* Always at least one empty line. */
258 iInputLine line; 280 iInputLine line;
259 init_InputLine(&line); 281 init_InputLine(&line);
282 line.offset = charPos;
260 pushBack_Array(&d->lines, &line); 283 pushBack_Array(&d->lines, &line);
261 } 284 }
262 else { 285 else {
@@ -578,7 +601,11 @@ void begin_InputWidget(iInputWidget *d) {
578 } 601 }
579 updateCursorLine_InputWidget_(d); 602 updateCursorLine_InputWidget_(d);
580 SDL_StartTextInput(); 603 SDL_StartTextInput();
581 setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iTrue); 604 setFlags_Widget(w, selected_WidgetFlag, iTrue);
605 if (d->maxLayoutLines != iInvalidSize) {
606 /* This will extend beyond the arranged region. */
607 setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue);
608 }
582 showCursor_InputWidget_(d); 609 showCursor_InputWidget_(d);
583 refresh_Widget(w); 610 refresh_Widget(w);
584 d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); 611 d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d);
@@ -672,6 +699,10 @@ void setCursor_InputWidget(iInputWidget *d, size_t pos) {
672 showCursor_InputWidget_(d); 699 showCursor_InputWidget_(d);
673} 700}
674 701
702iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) {
703 return (const void *) line == constAt_Array(&d->lines, size_Array(&d->lines) - 1);
704}
705
675static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { 706static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) {
676 if (x <= 0) { 707 if (x <= 0) {
677 return line->offset; 708 return line->offset;
@@ -680,7 +711,7 @@ static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const
680 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); 711 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos);
681 size_t index = line->offset; 712 size_t index = line->offset;
682 if (endPos == constEnd_String(&line->text)) { 713 if (endPos == constEnd_String(&line->text)) {
683 index += length_String(&line->text); 714 index += line->len;
684 } 715 }
685 else { 716 else {
686 /* Need to know the actual character index. */ 717 /* Need to know the actual character index. */
@@ -690,6 +721,9 @@ static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const
690 index++; 721 index++;
691 } 722 }
692 } 723 }
724 if (!isLastLine_InputWidget_(d, line) && index == line->offset + line->len) {
725 index = iMax(index - 1, line->offset);
726 }
693 return index; 727 return index;
694} 728}
695 729
@@ -705,11 +739,6 @@ static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) {
705 newCursor = indexForRelativeX_InputWidget_(d, xPos, ++line); 739 newCursor = indexForRelativeX_InputWidget_(d, xPos, ++line);
706 } 740 }
707 if (newCursor != iInvalidPos) { 741 if (newCursor != iInvalidPos) {
708 /* Clamp it to the current line. */
709 newCursor = iMax(newCursor, line->offset);
710 newCursor = iMin(newCursor, line->offset + length_String(&line->text) -
711 /* last line is allowed to go to the cursorMax */
712 ((const void *) line < constAt_Array(&d->lines, numLines - 1) ? 1 : 0));
713 setCursor_InputWidget(d, newCursor); 742 setCursor_InputWidget(d, newCursor);
714 return iTrue; 743 return iTrue;
715 } 744 }
@@ -896,7 +925,7 @@ static iRanges lineRange_InputWidget_(const iInputWidget *d) {
896 return (iRanges){ 0, 0 }; 925 return (iRanges){ 0, 0 };
897 } 926 }
898 const iInputLine *line = line_InputWidget_(d, d->cursorLine); 927 const iInputLine *line = line_InputWidget_(d, d->cursorLine);
899 return (iRanges){ line->offset, line->offset + length_String(&line->text) }; 928 return (iRanges){ line->offset, line->offset + line->len };
900} 929}
901 930
902static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { 931static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) {
@@ -1094,6 +1123,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1094 return iTrue; 1123 return iTrue;
1095 } 1124 }
1096 } 1125 }
1126#if defined (iPlatformApple)
1127 if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) {
1128 switch (key) {
1129 case SDLK_UP:
1130 case SDLK_DOWN:
1131 setCursor_InputWidget(d, key == SDLK_UP ? 0 : curMax);
1132 refresh_Widget(d);
1133 return iTrue;
1134 }
1135 }
1136#endif
1097 d->lastCursor = d->cursor; 1137 d->lastCursor = d->cursor;
1098 switch (key) { 1138 switch (key) {
1099 case SDLK_INSERT: 1139 case SDLK_INSERT:
@@ -1187,7 +1227,12 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1187 break; 1227 break;
1188 case SDLK_HOME: 1228 case SDLK_HOME:
1189 case SDLK_END: 1229 case SDLK_END:
1190 setCursor_InputWidget(d, key == SDLK_HOME ? lineFirst : lineLast); 1230 if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) {
1231 setCursor_InputWidget(d, key == SDLK_HOME ? 0 : curMax);
1232 }
1233 else {
1234 setCursor_InputWidget(d, key == SDLK_HOME ? lineFirst : lineLast);
1235 }
1191 refresh_Widget(w); 1236 refresh_Widget(w);
1192 return iTrue; 1237 return iTrue;
1193 case SDLK_a: 1238 case SDLK_a:
@@ -1233,19 +1278,20 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1233 /* Allow focus switching. */ 1278 /* Allow focus switching. */
1234 return processEvent_Widget(as_Widget(d), ev); 1279 return processEvent_Widget(as_Widget(d), ev);
1235 case SDLK_UP: 1280 case SDLK_UP:
1236 if (moveCursorByLine_InputWidget_(d, -1)) {
1237 refresh_Widget(d);
1238 return iTrue;
1239 }
1240 /* For moving to lookup from url entry. */
1241 return processEvent_Widget(as_Widget(d), ev);
1242 case SDLK_DOWN: 1281 case SDLK_DOWN:
1243 if (moveCursorByLine_InputWidget_(d, +1)) { 1282 if (moveCursorByLine_InputWidget_(d, key == SDLK_UP ? -1 : +1)) {
1244 refresh_Widget(d); 1283 refresh_Widget(d);
1245 return iTrue; 1284 return iTrue;
1246 } 1285 }
1247 /* For moving to lookup from url entry. */ 1286 /* For moving to lookup from url entry. */
1248 return processEvent_Widget(as_Widget(d), ev); 1287 return processEvent_Widget(as_Widget(d), ev);
1288 case SDLK_PAGEUP:
1289 case SDLK_PAGEDOWN:
1290 for (int count = 0; count < 5; count++) {
1291 moveCursorByLine_InputWidget_(d, key == SDLK_PAGEUP ? -1 : +1);
1292 }
1293 refresh_Widget(d);
1294 return iTrue;
1249 } 1295 }
1250 if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { 1296 if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) {
1251 return iFalse; 1297 return iFalse;
@@ -1265,6 +1311,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1265 return processEvent_Widget(w, ev); 1311 return processEvent_Widget(w, ev);
1266} 1312}
1267 1313
1314#if 0
1268static iBool isWhite_(const iString *str) { 1315static iBool isWhite_(const iString *str) {
1269 iConstForEach(String, i, str) { 1316 iConstForEach(String, i, str) {
1270 if (!isSpace_Char(i.value)) { 1317 if (!isSpace_Char(i.value)) {
@@ -1273,6 +1320,7 @@ static iBool isWhite_(const iString *str) {
1273 } 1320 }
1274 return iTrue; 1321 return iTrue;
1275} 1322}
1323#endif
1276 1324
1277static void draw_InputWidget_(const iInputWidget *d) { 1325static void draw_InputWidget_(const iInputWidget *d) {
1278 const iWidget *w = constAs_Widget(d); 1326 const iWidget *w = constAs_Widget(d);
@@ -1337,7 +1385,8 @@ static void draw_InputWidget_(const iInputWidget *d) {
1337 .x; 1385 .x;
1338 fillRect_Paint(&p, 1386 fillRect_Paint(&p,
1339 (iRect){ addX_I2(drawPos, iMin(m1, m2)), 1387 (iRect){ addX_I2(drawPos, iMin(m1, m2)),
1340 init_I2(iAbs(m2 - m1), lineHeight_Text(d->font)) }, 1388 init_I2(iMax(gap_UI / 3, iAbs(m2 - m1)),
1389 lineHeight_Text(d->font)) },
1341 uiMarked_ColorId); 1390 uiMarked_ColorId);
1342 } 1391 }
1343 } 1392 }
@@ -1405,13 +1454,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
1405 drawChildren_Widget(w); 1454 drawChildren_Widget(w);
1406} 1455}
1407 1456
1408//static void sizeChanged_InputWidget_(iInputWidget *d) {
1409// printf("[InputWidget] %p: size changed, updating layout\n", d);
1410// updateLinesAndResize_InputWidget_(d, iFalse);
1411//}
1412
1413iBeginDefineSubclass(InputWidget, Widget) 1457iBeginDefineSubclass(InputWidget, Widget)
1414 .processEvent = (iAny *) processEvent_InputWidget_, 1458 .processEvent = (iAny *) processEvent_InputWidget_,
1415 .draw = (iAny *) draw_InputWidget_, 1459 .draw = (iAny *) draw_InputWidget_,
1416// .sizeChanged = (iAny *) sizeChanged_InputWidget_,
1417iEndDefineSubclass(InputWidget) 1460iEndDefineSubclass(InputWidget)
diff --git a/src/ui/text.c b/src/ui/text.c
index 6c9e23cb..eca0952d 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -1269,7 +1269,7 @@ int drawWrapRange_Text(int fontId, iInt2 pos, int maxWidth, int color, iRangecc
1269 const iInt2 adv = tryAdvance_Text(fontId, text, maxWidth, &endp); 1269 const iInt2 adv = tryAdvance_Text(fontId, text, maxWidth, &endp);
1270 drawRange_Text(fontId, pos, color, (iRangecc){ text.start, endp }); 1270 drawRange_Text(fontId, pos, color, (iRangecc){ text.start, endp });
1271 text.start = endp; 1271 text.start = endp;
1272 pos.y += adv.y; 1272 pos.y += iMax(adv.y, lineHeight_Text(fontId));
1273 } 1273 }
1274 return pos.y; 1274 return pos.y;
1275} 1275}
@@ -1416,7 +1416,7 @@ static void initWrap_TextBuf_(iTextBuf *d, int font, int color, int maxWidth, iB
1416 const iInt2 size = (doWrap ? tryAdvance_Text(font, content, maxWidth, &content.start) 1416 const iInt2 size = (doWrap ? tryAdvance_Text(font, content, maxWidth, &content.start)
1417 : tryAdvanceNoWrap_Text(font, content, maxWidth, &content.start)); 1417 : tryAdvanceNoWrap_Text(font, content, maxWidth, &content.start));
1418 d->size.x = iMax(d->size.x, size.x); 1418 d->size.x = iMax(d->size.x, size.x);
1419 d->size.y += size.y; 1419 d->size.y += iMax(size.y, lineHeight_Text(font));
1420 } 1420 }
1421 } 1421 }
1422 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); 1422 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
diff --git a/src/ui/touch.c b/src/ui/touch.c
index b2c52526..c5634788 100644
--- a/src/ui/touch.c
+++ b/src/ui/touch.c
@@ -338,19 +338,6 @@ static void update_TouchState_(void *ptr) {
338 } 338 }
339} 339}
340 340
341static iWidget *findOverflowScrollable_Widget_(iWidget *d) {
342 const iInt2 rootSize = size_Root(d->root);
343 for (iWidget *w = d; w; w = parent_Widget(w)) {
344 if (flags_Widget(w) & overflowScrollable_WidgetFlag) {
345 if (height_Widget(w) > rootSize.y && !hasVisibleChildOnTop_Widget(w)) {
346 return w;
347 }
348 return NULL;
349 }
350 }
351 return NULL;
352}
353
354static iWidget *findSlidePanel_Widget_(iWidget *d) { 341static iWidget *findSlidePanel_Widget_(iWidget *d) {
355 for (iWidget *w = d; w; w = parent_Widget(w)) { 342 for (iWidget *w = d; w; w = parent_Widget(w)) {
356 if (isVisible_Widget(w) && flags_Widget(w) & horizontalOffset_WidgetFlag) { 343 if (isVisible_Widget(w) && flags_Widget(w) & horizontalOffset_WidgetFlag) {
@@ -544,7 +531,7 @@ iBool processEvent_Touch(const SDL_Event *ev) {
544 divvf_F3(&touch->accum, 6); 531 divvf_F3(&touch->accum, 6);
545 divfv_I2(&pixels, 6); 532 divfv_I2(&pixels, 6);
546 /* Allow scrolling a scrollable widget. */ 533 /* Allow scrolling a scrollable widget. */
547 iWidget *flow = findOverflowScrollable_Widget_(touch->affinity); 534 iWidget *flow = findOverflowScrollable_Widget(touch->affinity);
548 if (flow) { 535 if (flow) {
549 touch->affinity = flow; 536 touch->affinity = flow;
550 } 537 }
diff --git a/src/ui/widget.c b/src/ui/widget.c
index fa05e8d1..18f98050 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -927,7 +927,7 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
927 return iFalse; 927 return iFalse;
928} 928}
929 929
930static iBool scrollOverflow_Widget_(iWidget *d, int delta) { 930iBool scrollOverflow_Widget(iWidget *d, int delta) {
931 iRect bounds = bounds_Widget(d); 931 iRect bounds = bounds_Widget(d);
932 const iInt2 rootSize = size_Root(d->root); 932 const iInt2 rootSize = size_Root(d->root);
933 const iRect winRect = safeRect_Root(d->root); 933 const iRect winRect = safeRect_Root(d->root);
@@ -980,7 +980,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
980 if (!isPerPixel_MouseWheelEvent(&ev->wheel)) { 980 if (!isPerPixel_MouseWheelEvent(&ev->wheel)) {
981 step *= lineHeight_Text(uiLabel_FontId); 981 step *= lineHeight_Text(uiLabel_FontId);
982 } 982 }
983 if (scrollOverflow_Widget_(d, step)) { 983 if (scrollOverflow_Widget(d, step)) {
984 return iTrue; 984 return iTrue;
985 } 985 }
986 } 986 }
@@ -989,7 +989,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
989 if (d->flags & overflowScrollable_WidgetFlag && 989 if (d->flags & overflowScrollable_WidgetFlag &&
990 ~d->flags & visualOffset_WidgetFlag && 990 ~d->flags & visualOffset_WidgetFlag &&
991 isCommand_UserEvent(ev, "widget.overflow")) { 991 isCommand_UserEvent(ev, "widget.overflow")) {
992 scrollOverflow_Widget_(d, 0); /* check bounds */ 992 scrollOverflow_Widget(d, 0); /* check bounds */
993 } 993 }
994 if (ev->user.code == command_UserEventCode && d->commandHandler && 994 if (ev->user.code == command_UserEventCode && d->commandHandler &&
995 d->commandHandler(d, ev->user.data1)) { 995 d->commandHandler(d, ev->user.data1)) {
@@ -1321,6 +1321,22 @@ iAny *findParentClass_Widget(const iWidget *d, const iAnyClass *class) {
1321 return i; 1321 return i;
1322} 1322}
1323 1323
1324iAny *findOverflowScrollable_Widget(iWidget *d) {
1325 const iRect rootRect = rect_Root(d->root);
1326 for (iWidget *w = d; w; w = parent_Widget(w)) {
1327 if (flags_Widget(w) & overflowScrollable_WidgetFlag) {
1328 const iRect bounds = boundsWithoutVisualOffset_Widget(w);
1329 if ((bottom_Rect(bounds) > bottom_Rect(rootRect) ||
1330 top_Rect(bounds) < top_Rect(rootRect)) &&
1331 !hasVisibleChildOnTop_Widget(w)) {
1332 return w;
1333 }
1334 return NULL;
1335 }
1336 }
1337 return NULL;
1338}
1339
1324size_t childCount_Widget(const iWidget *d) { 1340size_t childCount_Widget(const iWidget *d) {
1325 if (!d->children) return 0; 1341 if (!d->children) return 0;
1326 return size_ObjectList(d->children); 1342 return size_ObjectList(d->children);
diff --git a/src/ui/widget.h b/src/ui/widget.h
index 5c05e917..13f400d1 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -193,6 +193,7 @@ iAny * findChild_Widget (const iWidget *, const char *id);
193const iPtrArray *findChildren_Widget (const iWidget *, const char *id); 193const iPtrArray *findChildren_Widget (const iWidget *, const char *id);
194iAny * findParentClass_Widget (const iWidget *, const iAnyClass *class); 194iAny * findParentClass_Widget (const iWidget *, const iAnyClass *class);
195iAny * findFocusable_Widget (const iWidget *startFrom, enum iWidgetFocusDir focusDir); 195iAny * findFocusable_Widget (const iWidget *startFrom, enum iWidgetFocusDir focusDir);
196iAny * findOverflowScrollable_Widget (iWidget *);
196size_t childCount_Widget (const iWidget *); 197size_t childCount_Widget (const iWidget *);
197void draw_Widget (const iWidget *); 198void draw_Widget (const iWidget *);
198void drawBackground_Widget (const iWidget *); 199void drawBackground_Widget (const iWidget *);
@@ -262,6 +263,7 @@ iAny * child_Widget (iWidget *, size_t index); /* O(n) */
262size_t childIndex_Widget (const iWidget *, const iAnyObject *child); /* O(n) */ 263size_t childIndex_Widget (const iWidget *, const iAnyObject *child); /* O(n) */
263void arrange_Widget (iWidget *); 264void arrange_Widget (iWidget *);
264void resetSize_Widget (iWidget *); 265void resetSize_Widget (iWidget *);
266iBool scrollOverflow_Widget (iWidget *, int delta); /* moves the widget */
265iBool dispatchEvent_Widget (iWidget *, const SDL_Event *); 267iBool dispatchEvent_Widget (iWidget *, const SDL_Event *);
266iBool processEvent_Widget (iWidget *, const SDL_Event *); 268iBool processEvent_Widget (iWidget *, const SDL_Event *);
267void postCommand_Widget (const iAnyObject *, const char *cmd, ...); 269void postCommand_Widget (const iAnyObject *, const char *cmd, ...);