diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-05-12 09:06:11 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-05-12 09:06:11 +0300 |
commit | 4e84a9ff371e69ba828f8678c5cec89eea45d2ab (patch) | |
tree | 332a76e480b197c1bc814ff8dd4d9b46ebe4431e /src/ui/inputwidget.c | |
parent | 892d8dedc46713ddf916e82405f06fc2c9c46ae4 (diff) |
InputWidget: Expanding while editing
The navbar layout cannot accommodate more than one line of text, but allow the editor to expand while writing text.
Diffstat (limited to 'src/ui/inputwidget.c')
-rw-r--r-- | src/ui/inputwidget.c | 61 |
1 files changed, 50 insertions, 11 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 22dd4c92..0fd80ca5 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -104,6 +104,7 @@ struct Impl_InputWidget { | |||
104 | enum iInputMode mode; | 104 | enum iInputMode mode; |
105 | int inFlags; | 105 | int inFlags; |
106 | size_t maxLen; | 106 | size_t maxLen; |
107 | size_t maxLayoutLines; | ||
107 | iArray text; /* iChar[] */ | 108 | iArray text; /* iChar[] */ |
108 | iArray oldText; /* iChar[] */ | 109 | iArray oldText; /* iChar[] */ |
109 | iArray lines; | 110 | iArray lines; |
@@ -162,7 +163,8 @@ iLocalDef iInt2 padding_(void) { | |||
162 | } | 163 | } |
163 | 164 | ||
164 | static iRect contentBounds_InputWidget_(const iInputWidget *d) { | 165 | static iRect contentBounds_InputWidget_(const iInputWidget *d) { |
165 | const iWidget *w = constAs_Widget(d); | 166 | const iWidget *w = constAs_Widget(d); |
167 | const iRect widgetBounds = bounds_Widget(w); | ||
166 | iRect bounds = adjusted_Rect(bounds_Widget(w), | 168 | iRect bounds = adjusted_Rect(bounds_Widget(w), |
167 | addX_I2(padding_(), d->leftPadding), | 169 | addX_I2(padding_(), d->leftPadding), |
168 | neg_I2(addX_I2(padding_(), d->rightPadding))); | 170 | neg_I2(addX_I2(padding_(), d->rightPadding))); |
@@ -233,7 +235,12 @@ static void updateLines_InputWidget_(iInputWidget *d) { | |||
233 | const int wrapWidth = contentBounds_InputWidget_(d).size.x; | 235 | const int wrapWidth = contentBounds_InputWidget_(d).size.x; |
234 | while (wrapWidth > 0 && content.end != content.start) { | 236 | while (wrapWidth > 0 && content.end != content.start) { |
235 | const char *endPos; | 237 | const char *endPos; |
236 | tryAdvance_Text(d->font, content, wrapWidth, &endPos); | 238 | if (d->inFlags & isUrl_InputWidgetFlag) { |
239 | tryAdvanceNoWrap_Text(d->font, content, wrapWidth, &endPos); | ||
240 | } | ||
241 | else { | ||
242 | tryAdvance_Text(d->font, content, wrapWidth, &endPos); | ||
243 | } | ||
237 | const iRangecc part = (iRangecc){ content.start, endPos }; | 244 | const iRangecc part = (iRangecc){ content.start, endPos }; |
238 | iInputLine line; | 245 | iInputLine line; |
239 | init_InputLine(&line); | 246 | init_InputLine(&line); |
@@ -256,15 +263,19 @@ static void updateLines_InputWidget_(iInputWidget *d) { | |||
256 | updateCursorLine_InputWidget_(d); | 263 | updateCursorLine_InputWidget_(d); |
257 | } | 264 | } |
258 | 265 | ||
259 | static int contentHeight_InputWidget_(const iInputWidget *d) { | 266 | static int contentHeight_InputWidget_(const iInputWidget *d, iBool forLayout) { |
260 | return iMax(1, size_Array(&d->lines)) * lineHeight_Text(d->font); | 267 | size_t numLines = iMax(1, size_Array(&d->lines)); |
268 | if (forLayout) { | ||
269 | numLines = iMin(numLines, d->maxLayoutLines); | ||
270 | } | ||
271 | return numLines * lineHeight_Text(d->font); | ||
261 | } | 272 | } |
262 | 273 | ||
263 | static void updateMetrics_InputWidget_(iInputWidget *d) { | 274 | static void updateMetrics_InputWidget_(iInputWidget *d) { |
264 | iWidget *w = as_Widget(d); | 275 | iWidget *w = as_Widget(d); |
265 | updateSizeForFixedLength_InputWidget_(d); | 276 | updateSizeForFixedLength_InputWidget_(d); |
266 | /* Caller must arrange the width, but the height is fixed. */ | 277 | /* Caller must arrange the width, but the height is fixed. */ |
267 | w->rect.size.y = contentHeight_InputWidget_(d) + 3 * padding_().y; /* TODO: Why 3x? */ | 278 | w->rect.size.y = contentHeight_InputWidget_(d, iTrue) + 3 * padding_().y; /* TODO: Why 3x? */ |
268 | if (flags_Widget(w) & extraPadding_WidgetFlag) { | 279 | if (flags_Widget(w) & extraPadding_WidgetFlag) { |
269 | w->rect.size.y += 2 * gap_UI; | 280 | w->rect.size.y += 2 * gap_UI; |
270 | } | 281 | } |
@@ -276,6 +287,7 @@ static void updateLinesAndResize_InputWidget_(iInputWidget *d) { | |||
276 | const size_t oldCount = size_Array(&d->lines); | 287 | const size_t oldCount = size_Array(&d->lines); |
277 | updateLines_InputWidget_(d); | 288 | updateLines_InputWidget_(d); |
278 | if (oldCount != size_Array(&d->lines)) { | 289 | if (oldCount != size_Array(&d->lines)) { |
290 | d->click.minHeight = contentHeight_InputWidget_(d, iFalse); | ||
279 | updateMetrics_InputWidget_(d); | 291 | updateMetrics_InputWidget_(d); |
280 | } | 292 | } |
281 | } | 293 | } |
@@ -303,6 +315,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
303 | d->inFlags = eatEscape_InputWidgetFlag; | 315 | d->inFlags = eatEscape_InputWidgetFlag; |
304 | iZap(d->mark); | 316 | iZap(d->mark); |
305 | setMaxLen_InputWidget(d, maxLen); | 317 | setMaxLen_InputWidget(d, maxLen); |
318 | d->maxLayoutLines = iInvalidSize; | ||
306 | setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); | 319 | setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); |
307 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 320 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
308 | d->timer = 0; | 321 | d->timer = 0; |
@@ -405,6 +418,11 @@ void setMaxLen_InputWidget(iInputWidget *d, size_t maxLen) { | |||
405 | updateSizeForFixedLength_InputWidget_(d); | 418 | updateSizeForFixedLength_InputWidget_(d); |
406 | } | 419 | } |
407 | 420 | ||
421 | void setMaxLayoutLines_InputWidget(iInputWidget *d, size_t maxLayoutLines) { | ||
422 | d->maxLayoutLines = maxLayoutLines; | ||
423 | updateMetrics_InputWidget_(d); | ||
424 | } | ||
425 | |||
408 | void setHint_InputWidget(iInputWidget *d, const char *hintText) { | 426 | void setHint_InputWidget(iInputWidget *d, const char *hintText) { |
409 | /* Keep original for retranslations. */ | 427 | /* Keep original for retranslations. */ |
410 | setCStr_String(&d->srcHint, hintText); | 428 | setCStr_String(&d->srcHint, hintText); |
@@ -532,7 +550,7 @@ void begin_InputWidget(iInputWidget *d) { | |||
532 | } | 550 | } |
533 | updateCursorLine_InputWidget_(d); | 551 | updateCursorLine_InputWidget_(d); |
534 | SDL_StartTextInput(); | 552 | SDL_StartTextInput(); |
535 | setFlags_Widget(w, selected_WidgetFlag, iTrue); | 553 | setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iTrue); |
536 | showCursor_InputWidget_(d); | 554 | showCursor_InputWidget_(d); |
537 | refresh_Widget(w); | 555 | refresh_Widget(w); |
538 | d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); | 556 | d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); |
@@ -560,7 +578,7 @@ void end_InputWidget(iInputWidget *d, iBool accept) { | |||
560 | SDL_RemoveTimer(d->timer); | 578 | SDL_RemoveTimer(d->timer); |
561 | d->timer = 0; | 579 | d->timer = 0; |
562 | SDL_StopTextInput(); | 580 | SDL_StopTextInput(); |
563 | setFlags_Widget(w, selected_WidgetFlag, iFalse); | 581 | setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iFalse); |
564 | const char *id = cstr_String(id_Widget(as_Widget(d))); | 582 | const char *id = cstr_String(id_Widget(as_Widget(d))); |
565 | if (!*id) id = "_"; | 583 | if (!*id) id = "_"; |
566 | updateLinesAndResize_InputWidget_(d); | 584 | updateLinesAndResize_InputWidget_(d); |
@@ -866,6 +884,20 @@ static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { | |||
866 | } | 884 | } |
867 | } | 885 | } |
868 | 886 | ||
887 | static iRect bounds_InputWidget_(const iInputWidget *d) { | ||
888 | const iWidget *w = constAs_Widget(d); | ||
889 | iRect bounds = bounds_Widget(w); | ||
890 | if (!isFocused_Widget(d)) { | ||
891 | return bounds; | ||
892 | } | ||
893 | bounds.size.y = contentHeight_InputWidget_(d, iFalse) + 3 * padding_().y; | ||
894 | return bounds; | ||
895 | } | ||
896 | |||
897 | static iBool contains_InputWidget_(const iInputWidget *d, iInt2 coord) { | ||
898 | return contains_Rect(bounds_InputWidget_(d), coord); | ||
899 | } | ||
900 | |||
869 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 901 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { |
870 | iWidget *w = as_Widget(d); | 902 | iWidget *w = as_Widget(d); |
871 | if (isCommand_Widget(w, ev, "focus.gained")) { | 903 | if (isCommand_Widget(w, ev, "focus.gained")) { |
@@ -925,8 +957,9 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
925 | copy_InputWidget_(d, iFalse); | 957 | copy_InputWidget_(d, iFalse); |
926 | return iTrue; | 958 | return iTrue; |
927 | } | 959 | } |
928 | if (ev->type == SDL_MOUSEMOTION && isHover_Widget(d)) { | 960 | if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { |
929 | const iInt2 inner = windowToInner_Widget(w, init_I2(ev->motion.x, ev->motion.y)); | 961 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); |
962 | const iInt2 inner = windowToInner_Widget(w, coord); | ||
930 | setCursor_Window(get_Window(), | 963 | setCursor_Window(get_Window(), |
931 | inner.x >= 2 * gap_UI + d->leftPadding && | 964 | inner.x >= 2 * gap_UI + d->leftPadding && |
932 | inner.x < width_Widget(w) - d->rightPadding | 965 | inner.x < width_Widget(w) - d->rightPadding |
@@ -976,6 +1009,12 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
976 | d->inFlags &= ~isMarking_InputWidgetFlag; | 1009 | d->inFlags &= ~isMarking_InputWidgetFlag; |
977 | return iTrue; | 1010 | return iTrue; |
978 | } | 1011 | } |
1012 | if (ev->type == SDL_MOUSEMOTION && flags_Widget(w) & keepOnTop_WidgetFlag) { | ||
1013 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); | ||
1014 | if (contains_Click(&d->click, coord)) { | ||
1015 | return iTrue; | ||
1016 | } | ||
1017 | } | ||
979 | if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && | 1018 | if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && |
980 | contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { | 1019 | contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { |
981 | iWidget *clipMenu = findWidget_App("clipmenu"); | 1020 | iWidget *clipMenu = findWidget_App("clipmenu"); |
@@ -1193,11 +1232,11 @@ static iBool isWhite_(const iString *str) { | |||
1193 | 1232 | ||
1194 | static void draw_InputWidget_(const iInputWidget *d) { | 1233 | static void draw_InputWidget_(const iInputWidget *d) { |
1195 | const iWidget *w = constAs_Widget(d); | 1234 | const iWidget *w = constAs_Widget(d); |
1196 | iRect bounds = adjusted_Rect(bounds_Widget(w), padding_(), neg_I2(padding_())); | 1235 | iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); |
1197 | iBool isHint = iFalse; | 1236 | iBool isHint = iFalse; |
1198 | const iBool isFocused = isFocused_Widget(w); | 1237 | const iBool isFocused = isFocused_Widget(w); |
1199 | const iBool isHover = isHover_Widget(w) && | 1238 | const iBool isHover = isHover_Widget(w) && |
1200 | contains_Widget(w, mouseCoord_Window(get_Window())); | 1239 | contains_InputWidget_(d, mouseCoord_Window(get_Window())); |
1201 | if (d->inFlags & needUpdateBuffer_InputWidgetFlag) { | 1240 | if (d->inFlags & needUpdateBuffer_InputWidgetFlag) { |
1202 | updateBuffered_InputWidget_(iConstCast(iInputWidget *, d)); | 1241 | updateBuffered_InputWidget_(iConstCast(iInputWidget *, d)); |
1203 | } | 1242 | } |