summaryrefslogtreecommitdiff
path: root/src/ui/inputwidget.c
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 /src/ui/inputwidget.c
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.
Diffstat (limited to 'src/ui/inputwidget.c')
-rw-r--r--src/ui/inputwidget.c123
1 files changed, 83 insertions, 40 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)