diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/app.c | 21 | ||||
-rw-r--r-- | src/app.h | 11 | ||||
-rw-r--r-- | src/gmdocument.c | 2 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 68 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 543 | ||||
-rw-r--r-- | src/ui/inputwidget.h | 5 | ||||
-rw-r--r-- | src/ui/root.c | 31 | ||||
-rw-r--r-- | src/ui/root.h | 4 | ||||
-rw-r--r-- | src/ui/text.c | 7 | ||||
-rw-r--r-- | src/ui/util.c | 40 | ||||
-rw-r--r-- | src/ui/util.h | 2 | ||||
-rw-r--r-- | src/ui/widget.c | 13 | ||||
-rw-r--r-- | src/ui/widget.h | 1 |
14 files changed, 591 insertions, 159 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 84e81e9f..17fded2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -18,7 +18,7 @@ | |||
18 | cmake_minimum_required (VERSION 3.9) | 18 | cmake_minimum_required (VERSION 3.9) |
19 | 19 | ||
20 | project (Lagrange | 20 | project (Lagrange |
21 | VERSION 1.4.1 | 21 | VERSION 1.5.0 |
22 | DESCRIPTION "A Beautiful Gemini Client" | 22 | DESCRIPTION "A Beautiful Gemini Client" |
23 | LANGUAGES C | 23 | LANGUAGES C |
24 | ) | 24 | ) |
@@ -1049,6 +1049,23 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1049 | } | 1049 | } |
1050 | continue; | 1050 | continue; |
1051 | } | 1051 | } |
1052 | else if (ev.type == SDL_USEREVENT && ev.user.code == arrange_UserEventCode) { | ||
1053 | printf("[App] rearrange\n"); | ||
1054 | resize_Window(d->window, -1, -1); | ||
1055 | iForIndices(i, d->window->roots) { | ||
1056 | if (d->window->roots[i]) { | ||
1057 | d->window->roots[i]->pendingArrange = iFalse; | ||
1058 | } | ||
1059 | } | ||
1060 | // if (ev.user.data2 == d->window->roots[0]) { | ||
1061 | // arrange_Widget(d->window->roots[0]->widget); | ||
1062 | // } | ||
1063 | // else if (d->window->roots[1]) { | ||
1064 | // arrange_Widget(d->window->roots[1]->widget); | ||
1065 | // } | ||
1066 | // postRefresh_App(); | ||
1067 | continue; | ||
1068 | } | ||
1052 | d->lastEventTime = SDL_GetTicks(); | 1069 | d->lastEventTime = SDL_GetTicks(); |
1053 | if (d->isIdling) { | 1070 | if (d->isIdling) { |
1054 | // printf("[App] ...woke up\n"); | 1071 | // printf("[App] ...woke up\n"); |
@@ -1561,6 +1578,10 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | |||
1561 | setToggle_Widget(findChild_Widget(d, "prefs.ostheme"), iFalse); | 1578 | setToggle_Widget(findChild_Widget(d, "prefs.ostheme"), iFalse); |
1562 | } | 1579 | } |
1563 | } | 1580 | } |
1581 | else if (equalWidget_Command(cmd, d, "input.resized")) { | ||
1582 | updatePreferencesLayout_Widget(d); | ||
1583 | return iFalse; | ||
1584 | } | ||
1564 | return iFalse; | 1585 | return iFalse; |
1565 | } | 1586 | } |
1566 | 1587 | ||
@@ -57,14 +57,15 @@ enum iAppEventMode { | |||
57 | 57 | ||
58 | enum iUserEventCode { | 58 | enum iUserEventCode { |
59 | command_UserEventCode = 1, | 59 | command_UserEventCode = 1, |
60 | refresh_UserEventCode = 2, | 60 | refresh_UserEventCode, |
61 | asleep_UserEventCode = 3, | 61 | arrange_UserEventCode, |
62 | asleep_UserEventCode, | ||
62 | /* The start of a potential touch tap event is notified via a custom event because | 63 | /* The start of a potential touch tap event is notified via a custom event because |
63 | sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will | 64 | sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will |
64 | take, it could turn into a tap-and-hold for example. */ | 65 | take, it could turn into a tap-and-hold for example. */ |
65 | widgetTapBegins_UserEventCode = 4, | 66 | widgetTapBegins_UserEventCode, |
66 | widgetTouchEnds_UserEventCode = 5, /* finger lifted, but momentum may continue */ | 67 | widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */ |
67 | immediateRefresh_UserEventCode = 6, /* refresh even though more events are pending */ | 68 | immediateRefresh_UserEventCode, /* refresh even though more events are pending */ |
68 | }; | 69 | }; |
69 | 70 | ||
70 | const iString *execPath_App (void); | 71 | const iString *execPath_App (void); |
diff --git a/src/gmdocument.c b/src/gmdocument.c index da99fd0d..4fc0dd5e 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -726,7 +726,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
726 | const int wrapAvail = d->size.x - run.bounds.pos.x - rightMargin * gap_Text; | 726 | const int wrapAvail = d->size.x - run.bounds.pos.x - rightMargin * gap_Text; |
727 | const int avail = isWordWrapped ? wrapAvail : 0; | 727 | const int avail = isWordWrapped ? wrapAvail : 0; |
728 | const char *contPos; | 728 | const char *contPos; |
729 | const iInt2 dims = tryAdvance_Text(run.font, runLine, avail, &contPos); | 729 | const iInt2 dims = tryAdvance_Text(run.font, runLine, avail, &contPos); |
730 | iChangeFlags(run.flags, wide_GmRunFlag, (isPreformat && dims.x > d->size.x)); | 730 | iChangeFlags(run.flags, wide_GmRunFlag, (isPreformat && dims.x > d->size.x)); |
731 | run.bounds.size.x = iMax(wrapAvail, dims.x); /* Extends to the right edge for selection. */ | 731 | run.bounds.size.x = iMax(wrapAvail, dims.x); /* Extends to the right edge for selection. */ |
732 | run.bounds.size.y = dims.y; | 732 | run.bounds.size.y = dims.y; |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 048f8ce4..d23e95a6 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -304,7 +304,7 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
304 | iWidget *w = as_Widget(d); | 304 | iWidget *w = as_Widget(d); |
305 | init_Widget(w); | 305 | init_Widget(w); |
306 | setId_Widget(w, format_CStr("document%03d", ++docEnum_)); | 306 | setId_Widget(w, format_CStr("document%03d", ++docEnum_)); |
307 | setFlags_Widget(w, hover_WidgetFlag, iTrue); | 307 | setFlags_Widget(w, hover_WidgetFlag | noBackground_WidgetFlag, iTrue); |
308 | init_PersistentDocumentState(&d->mod); | 308 | init_PersistentDocumentState(&d->mod); |
309 | d->flags = 0; | 309 | d->flags = 0; |
310 | d->phoneToolbar = NULL; | 310 | d->phoneToolbar = NULL; |
@@ -1571,6 +1571,43 @@ static void togglePreFold_DocumentWidget_(iDocumentWidget *d, uint16_t preId) { | |||
1571 | refresh_Widget(as_Widget(d)); | 1571 | refresh_Widget(as_Widget(d)); |
1572 | } | 1572 | } |
1573 | 1573 | ||
1574 | static iString *makeQueryUrl_DocumentWidget_(const iDocumentWidget *d, | ||
1575 | const iString *userEnteredText) { | ||
1576 | iString *url = copy_String(d->mod.url); | ||
1577 | /* Remove the existing query string. */ | ||
1578 | const size_t qPos = indexOfCStr_String(url, "?"); | ||
1579 | if (qPos != iInvalidPos) { | ||
1580 | remove_Block(&url->chars, qPos, iInvalidSize); | ||
1581 | } | ||
1582 | appendCStr_String(url, "?"); | ||
1583 | append_String(url, collect_String(urlEncode_String(userEnteredText))); | ||
1584 | return url; | ||
1585 | } | ||
1586 | |||
1587 | static void inputQueryValidator_(iInputWidget *input, void *context) { | ||
1588 | iDocumentWidget *d = context; | ||
1589 | iString *url = makeQueryUrl_DocumentWidget_(d, text_InputWidget(input)); | ||
1590 | iWidget *dlg = parent_Widget(input); | ||
1591 | iLabelWidget *counter = findChild_Widget(dlg, "valueinput.counter"); | ||
1592 | iAssert(counter); | ||
1593 | int avail = 1024 - (int) size_String(url); | ||
1594 | setFlags_Widget(findChild_Widget(dlg, "default"), disabled_WidgetFlag, avail < 0); | ||
1595 | setEnterKeyEnabled_InputWidget(input, avail >= 0); | ||
1596 | int len = length_String(text_InputWidget(input)); | ||
1597 | if (len > 1024) { | ||
1598 | iString *trunc = copy_String(text_InputWidget(input)); | ||
1599 | truncate_String(trunc, 1024); | ||
1600 | setText_InputWidget(input, trunc); | ||
1601 | delete_String(trunc); | ||
1602 | } | ||
1603 | setTextCStr_LabelWidget(counter, format_CStr("%d", avail)); /* Gemini URL maxlen */ | ||
1604 | setTextColor_LabelWidget(counter, | ||
1605 | avail < 0 ? uiTextCaution_ColorId : | ||
1606 | avail < 128 ? uiTextStrong_ColorId | ||
1607 | : uiTextDim_ColorId); | ||
1608 | delete_String(url); | ||
1609 | } | ||
1610 | |||
1574 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | 1611 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { |
1575 | if (!d->request) { | 1612 | if (!d->request) { |
1576 | return; | 1613 | return; |
@@ -1598,13 +1635,21 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
1598 | isEmpty_String(&resp->meta) | 1635 | isEmpty_String(&resp->meta) |
1599 | ? format_CStr(cstr_Lang("dlg.input.prompt"), cstr_Rangecc(parts.path)) | 1636 | ? format_CStr(cstr_Lang("dlg.input.prompt"), cstr_Rangecc(parts.path)) |
1600 | : cstr_String(&resp->meta), | 1637 | : cstr_String(&resp->meta), |
1601 | uiTextCaution_ColorEscape "${dlg.input.send} \u21d2", | 1638 | uiTextCaution_ColorEscape "${dlg.input.send}", |
1602 | format_CStr("!document.input.submit doc:%p", d)); | 1639 | format_CStr("!document.input.submit doc:%p", d)); |
1640 | setId_Widget(addChildPosFlags_Widget(findChild_Widget(dlg, "dialogbuttons"), | ||
1641 | iClob(new_LabelWidget("", NULL)), | ||
1642 | front_WidgetAddPos, frameless_WidgetFlag), | ||
1643 | "valueinput.counter"); | ||
1644 | setValidator_InputWidget(findChild_Widget(dlg, "input"), inputQueryValidator_, d); | ||
1603 | setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"), | 1645 | setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"), |
1604 | statusCode == sensitiveInput_GmStatusCode); | 1646 | statusCode == sensitiveInput_GmStatusCode); |
1605 | if (document_App() != d) { | 1647 | if (document_App() != d) { |
1606 | postCommandf_App("tabs.switch page:%p", d); | 1648 | postCommandf_App("tabs.switch page:%p", d); |
1607 | } | 1649 | } |
1650 | else { | ||
1651 | updateTheme_DocumentWidget_(d); | ||
1652 | } | ||
1608 | break; | 1653 | break; |
1609 | } | 1654 | } |
1610 | case categorySuccess_GmStatusCode: | 1655 | case categorySuccess_GmStatusCode: |
@@ -2229,17 +2274,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2229 | return iTrue; | 2274 | return iTrue; |
2230 | } | 2275 | } |
2231 | else if (equal_Command(cmd, "document.input.submit") && document_Command(cmd) == d) { | 2276 | else if (equal_Command(cmd, "document.input.submit") && document_Command(cmd) == d) { |
2232 | iString *value = suffix_Command(cmd, "value"); | 2277 | postCommandf_Root(w->root, |
2233 | set_String(value, collect_String(urlEncode_String(value))); | 2278 | "open url:%s", |
2234 | iString *url = collect_String(copy_String(d->mod.url)); | 2279 | cstrCollect_String(makeQueryUrl_DocumentWidget_ |
2235 | const size_t qPos = indexOfCStr_String(url, "?"); | 2280 | (d, collect_String(suffix_Command(cmd, "value"))))); |
2236 | if (qPos != iInvalidPos) { | ||
2237 | remove_Block(&url->chars, qPos, iInvalidSize); | ||
2238 | } | ||
2239 | appendCStr_String(url, "?"); | ||
2240 | append_String(url, value); | ||
2241 | postCommandf_Root(w->root, "open url:%s", cstr_String(url)); | ||
2242 | delete_String(value); | ||
2243 | return iTrue; | 2281 | return iTrue; |
2244 | } | 2282 | } |
2245 | else if (equal_Command(cmd, "valueinput.cancelled") && | 2283 | else if (equal_Command(cmd, "valueinput.cancelled") && |
@@ -4172,7 +4210,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
4172 | if (width_Rect(bounds) <= 0) { | 4210 | if (width_Rect(bounds) <= 0) { |
4173 | return; | 4211 | return; |
4174 | } | 4212 | } |
4175 | draw_Widget(w); | 4213 | // draw_Widget(w); |
4176 | if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { | 4214 | if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { |
4177 | updateTimestampBuf_DocumentWidget_(d); | 4215 | updateTimestampBuf_DocumentWidget_(d); |
4178 | } | 4216 | } |
@@ -4250,7 +4288,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
4250 | if (colorTheme_App() == pureWhite_ColorTheme) { | 4288 | if (colorTheme_App() == pureWhite_ColorTheme) { |
4251 | drawHLine_Paint(&ctx.paint, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId); | 4289 | drawHLine_Paint(&ctx.paint, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId); |
4252 | } | 4290 | } |
4253 | draw_Widget(w); | 4291 | drawChildren_Widget(w); |
4254 | /* Alt text. */ | 4292 | /* Alt text. */ |
4255 | const float altTextOpacity = value_Anim(&d->altTextOpacity) * 6 - 5; | 4293 | const float altTextOpacity = value_Anim(&d->altTextOpacity) * 6 - 5; |
4256 | if (d->hoverAltPre && altTextOpacity > 0) { | 4294 | if (d->hoverAltPre && altTextOpacity > 0) { |
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index a1635128..5f86f5bf 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -75,21 +75,48 @@ enum iInputWidgetFlag { | |||
75 | isMarking_InputWidgetFlag = iBit(7), | 75 | isMarking_InputWidgetFlag = iBit(7), |
76 | markWords_InputWidgetFlag = iBit(8), | 76 | markWords_InputWidgetFlag = iBit(8), |
77 | needUpdateBuffer_InputWidgetFlag = iBit(9), | 77 | needUpdateBuffer_InputWidgetFlag = iBit(9), |
78 | enterKeyEnabled_InputWidgetFlag = iBit(10), | ||
78 | }; | 79 | }; |
79 | 80 | ||
81 | /*----------------------------------------------------------------------------------------------*/ | ||
82 | |||
83 | iDeclareType(InputLine) | ||
84 | |||
85 | struct Impl_InputLine { | ||
86 | size_t offset; /* character position from the beginning */ | ||
87 | iString text; /* UTF-8 */ | ||
88 | }; | ||
89 | |||
90 | static void init_InputLine(iInputLine *d) { | ||
91 | d->offset = 0; | ||
92 | init_String(&d->text); | ||
93 | } | ||
94 | |||
95 | static void deinit_InputLine(iInputLine *d) { | ||
96 | deinit_String(&d->text); | ||
97 | } | ||
98 | |||
99 | iDefineTypeConstruction(InputLine) | ||
100 | |||
101 | /*----------------------------------------------------------------------------------------------*/ | ||
102 | |||
80 | struct Impl_InputWidget { | 103 | struct Impl_InputWidget { |
81 | iWidget widget; | 104 | iWidget widget; |
82 | enum iInputMode mode; | 105 | enum iInputMode mode; |
83 | int inFlags; | 106 | int inFlags; |
84 | size_t maxLen; | 107 | size_t maxLen; |
108 | size_t maxLayoutLines; | ||
85 | iArray text; /* iChar[] */ | 109 | iArray text; /* iChar[] */ |
86 | iArray oldText; /* iChar[] */ | 110 | iArray oldText; /* iChar[] */ |
111 | iArray lines; | ||
87 | iString hint; | 112 | iString hint; |
88 | iString srcHint; | 113 | iString srcHint; |
89 | int leftPadding; | 114 | int leftPadding; |
90 | int rightPadding; | 115 | int rightPadding; |
91 | size_t cursor; | 116 | size_t cursor; /* offset from beginning */ |
92 | size_t lastCursor; | 117 | size_t lastCursor; |
118 | size_t cursorLine; | ||
119 | int verticalMoveX; | ||
93 | iRanges mark; | 120 | iRanges mark; |
94 | iRanges initialMark; | 121 | iRanges initialMark; |
95 | iArray undoStack; | 122 | iArray undoStack; |
@@ -98,6 +125,8 @@ struct Impl_InputWidget { | |||
98 | int cursorVis; | 125 | int cursorVis; |
99 | uint32_t timer; | 126 | uint32_t timer; |
100 | iTextBuf * buffered; | 127 | iTextBuf * buffered; |
128 | iInputWidgetValidatorFunc validator; | ||
129 | void * validatorContext; | ||
101 | }; | 130 | }; |
102 | 131 | ||
103 | iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) | 132 | iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) |
@@ -109,8 +138,20 @@ static void clearUndo_InputWidget_(iInputWidget *d) { | |||
109 | clear_Array(&d->undoStack); | 138 | clear_Array(&d->undoStack); |
110 | } | 139 | } |
111 | 140 | ||
141 | static void updateCursorLine_InputWidget_(iInputWidget *d) { | ||
142 | d->cursorLine = 0; | ||
143 | iConstForEach(Array, i, &d->lines) { | ||
144 | const iInputLine *line = i.value; | ||
145 | if (line->offset > d->cursor) { | ||
146 | break; | ||
147 | } | ||
148 | d->cursorLine = index_ArrayConstIterator(&i); | ||
149 | } | ||
150 | } | ||
151 | |||
112 | static void showCursor_InputWidget_(iInputWidget *d) { | 152 | static void showCursor_InputWidget_(iInputWidget *d) { |
113 | d->cursorVis = 2; | 153 | d->cursorVis = 2; |
154 | updateCursorLine_InputWidget_(d); | ||
114 | } | 155 | } |
115 | 156 | ||
116 | static void invalidateBuffered_InputWidget_(iInputWidget *d) { | 157 | static void invalidateBuffered_InputWidget_(iInputWidget *d) { |
@@ -120,6 +161,21 @@ static void invalidateBuffered_InputWidget_(iInputWidget *d) { | |||
120 | } | 161 | } |
121 | } | 162 | } |
122 | 163 | ||
164 | iLocalDef iInt2 padding_(void) { | ||
165 | return init_I2(gap_UI / 2, gap_UI / 2); | ||
166 | } | ||
167 | |||
168 | static 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 | |||
123 | static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { | 179 | static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { |
124 | if (d->maxLen) { | 180 | if (d->maxLen) { |
125 | /* Set a fixed size based on maximum possible width of the text. */ | 181 | /* Set a fixed size based on maximum possible width of the text. */ |
@@ -135,29 +191,122 @@ static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { | |||
135 | } | 191 | } |
136 | } | 192 | } |
137 | 193 | ||
194 | static const iChar sensitiveChar_ = 0x25cf; /* black circle */ | ||
195 | |||
196 | static iString *utf32toUtf8_InputWidget_(const iInputWidget *d) { | ||
197 | return newUnicodeN_String(constData_Array(&d->text), size_Array(&d->text)); | ||
198 | } | ||
199 | |||
200 | static iString *visText_InputWidget_(const iInputWidget *d) { | ||
201 | iString *text; | ||
202 | if (~d->inFlags & isSensitive_InputWidgetFlag) { | ||
203 | text = utf32toUtf8_InputWidget_(d); | ||
204 | } | ||
205 | else { | ||
206 | text = new_String(); | ||
207 | for (size_t i = 0; i < size_Array(&d->text); ++i) { | ||
208 | appendChar_String(text, sensitiveChar_); | ||
209 | } | ||
210 | } | ||
211 | return text; | ||
212 | } | ||
213 | |||
214 | static void clearLines_InputWidget_(iInputWidget *d) { | ||
215 | iForEach(Array, i, &d->lines) { | ||
216 | deinit_InputLine(i.value); | ||
217 | } | ||
218 | clear_Array(&d->lines); | ||
219 | } | ||
220 | |||
221 | static void updateLines_InputWidget_(iInputWidget *d) { | ||
222 | clearLines_InputWidget_(d); | ||
223 | if (d->maxLen) { | ||
224 | /* Everything on a single line. */ | ||
225 | iInputLine line; | ||
226 | init_InputLine(&line); | ||
227 | iString *u8 = visText_InputWidget_(d); | ||
228 | set_String(&line.text, u8); | ||
229 | delete_String(u8); | ||
230 | pushBack_Array(&d->lines, &line); | ||
231 | updateCursorLine_InputWidget_(d); | ||
232 | return; | ||
233 | } | ||
234 | /* Word-wrapped lines. */ | ||
235 | iString *u8 = visText_InputWidget_(d); | ||
236 | size_t charPos = 0; | ||
237 | iRangecc content = range_String(u8); | ||
238 | const int wrapWidth = contentBounds_InputWidget_(d).size.x; | ||
239 | while (wrapWidth > 0 && content.end != content.start) { | ||
240 | const char *endPos; | ||
241 | if (d->inFlags & isUrl_InputWidgetFlag) { | ||
242 | tryAdvanceNoWrap_Text(d->font, content, wrapWidth, &endPos); | ||
243 | } | ||
244 | else { | ||
245 | tryAdvance_Text(d->font, content, wrapWidth, &endPos); | ||
246 | } | ||
247 | const iRangecc part = (iRangecc){ content.start, endPos }; | ||
248 | iInputLine line; | ||
249 | init_InputLine(&line); | ||
250 | setRange_String(&line.text, part); | ||
251 | line.offset = charPos; | ||
252 | pushBack_Array(&d->lines, &line); | ||
253 | charPos += length_String(&line.text); | ||
254 | content.start = endPos; | ||
255 | } | ||
256 | if (isEmpty_Array(&d->lines) || endsWith_String(u8, "\n")) { | ||
257 | /* Always at least one empty line. */ | ||
258 | iInputLine line; | ||
259 | init_InputLine(&line); | ||
260 | pushBack_Array(&d->lines, &line); | ||
261 | } | ||
262 | else { | ||
263 | iAssert(charPos == length_String(u8)); | ||
264 | } | ||
265 | delete_String(u8); | ||
266 | updateCursorLine_InputWidget_(d); | ||
267 | } | ||
268 | |||
269 | static int contentHeight_InputWidget_(const iInputWidget *d, iBool forLayout) { | ||
270 | size_t numLines = iMax(1, size_Array(&d->lines)); | ||
271 | if (forLayout) { | ||
272 | numLines = iMin(numLines, d->maxLayoutLines); | ||
273 | } | ||
274 | return numLines * lineHeight_Text(d->font); | ||
275 | } | ||
276 | |||
138 | static void updateMetrics_InputWidget_(iInputWidget *d) { | 277 | static void updateMetrics_InputWidget_(iInputWidget *d) { |
139 | iWidget *w = as_Widget(d); | 278 | iWidget *w = as_Widget(d); |
140 | updateSizeForFixedLength_InputWidget_(d); | 279 | updateSizeForFixedLength_InputWidget_(d); |
141 | /* Caller must arrange the width, but the height is fixed. */ | 280 | /* Caller must arrange the width, but the height is fixed. */ |
142 | w->rect.size.y = lineHeight_Text(d->font) * 1.3f; | 281 | w->rect.size.y = contentHeight_InputWidget_(d, iTrue) + 3 * padding_().y; /* TODO: Why 3x? */ |
143 | if (flags_Widget(w) & extraPadding_WidgetFlag) { | 282 | if (flags_Widget(w) & extraPadding_WidgetFlag) { |
144 | w->rect.size.y += 2 * gap_UI; | 283 | w->rect.size.y += 2 * gap_UI; |
145 | } | 284 | } |
146 | invalidateBuffered_InputWidget_(d); | 285 | invalidateBuffered_InputWidget_(d); |
147 | if (parent_Widget(w)) { | 286 | postCommand_Widget(d, "input.resized"); |
148 | arrange_Widget(w); | 287 | } |
288 | |||
289 | static void updateLinesAndResize_InputWidget_(iInputWidget *d) { | ||
290 | const size_t oldCount = size_Array(&d->lines); | ||
291 | updateLines_InputWidget_(d); | ||
292 | if (oldCount != size_Array(&d->lines)) { | ||
293 | d->click.minHeight = contentHeight_InputWidget_(d, iFalse); | ||
294 | updateMetrics_InputWidget_(d); | ||
149 | } | 295 | } |
150 | } | 296 | } |
151 | 297 | ||
152 | void init_InputWidget(iInputWidget *d, size_t maxLen) { | 298 | void init_InputWidget(iInputWidget *d, size_t maxLen) { |
153 | iWidget *w = &d->widget; | 299 | iWidget *w = &d->widget; |
154 | init_Widget(w); | 300 | init_Widget(w); |
301 | d->validator = NULL; | ||
302 | d->validatorContext = NULL; | ||
155 | setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag | touchDrag_WidgetFlag, iTrue); | 303 | setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag | touchDrag_WidgetFlag, iTrue); |
156 | #if defined (iPlatformMobile) | 304 | #if defined (iPlatformMobile) |
157 | setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); | 305 | setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); |
158 | #endif | 306 | #endif |
159 | init_Array(&d->text, sizeof(iChar)); | 307 | init_Array(&d->text, sizeof(iChar)); |
160 | init_Array(&d->oldText, sizeof(iChar)); | 308 | init_Array(&d->oldText, sizeof(iChar)); |
309 | init_Array(&d->lines, sizeof(iInputLine)); | ||
161 | init_String(&d->hint); | 310 | init_String(&d->hint); |
162 | init_String(&d->srcHint); | 311 | init_String(&d->srcHint); |
163 | init_Array(&d->undoStack, sizeof(iInputUndo)); | 312 | init_Array(&d->undoStack, sizeof(iInputUndo)); |
@@ -166,18 +315,23 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
166 | d->rightPadding = 0; | 315 | d->rightPadding = 0; |
167 | d->cursor = 0; | 316 | d->cursor = 0; |
168 | d->lastCursor = 0; | 317 | d->lastCursor = 0; |
169 | d->inFlags = eatEscape_InputWidgetFlag; | 318 | d->cursorLine = 0; |
319 | d->verticalMoveX = -1; /* TODO: Use this. */ | ||
320 | d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag; | ||
170 | iZap(d->mark); | 321 | iZap(d->mark); |
171 | setMaxLen_InputWidget(d, maxLen); | 322 | setMaxLen_InputWidget(d, maxLen); |
323 | d->maxLayoutLines = iInvalidSize; | ||
172 | setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); | 324 | setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); |
173 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 325 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
174 | d->timer = 0; | 326 | d->timer = 0; |
175 | d->cursorVis = 0; | 327 | d->cursorVis = 0; |
176 | d->buffered = NULL; | 328 | d->buffered = NULL; |
329 | updateLines_InputWidget_(d); | ||
177 | updateMetrics_InputWidget_(d); | 330 | updateMetrics_InputWidget_(d); |
178 | } | 331 | } |
179 | 332 | ||
180 | void deinit_InputWidget(iInputWidget *d) { | 333 | void deinit_InputWidget(iInputWidget *d) { |
334 | clearLines_InputWidget_(d); | ||
181 | if (isSelected_Widget(d)) { | 335 | if (isSelected_Widget(d)) { |
182 | SDL_StopTextInput(); | 336 | SDL_StopTextInput(); |
183 | enableEditorKeysInMenus_(iTrue); | 337 | enableEditorKeysInMenus_(iTrue); |
@@ -190,6 +344,7 @@ void deinit_InputWidget(iInputWidget *d) { | |||
190 | } | 344 | } |
191 | deinit_String(&d->srcHint); | 345 | deinit_String(&d->srcHint); |
192 | deinit_String(&d->hint); | 346 | deinit_String(&d->hint); |
347 | deinit_Array(&d->lines); | ||
193 | deinit_Array(&d->oldText); | 348 | deinit_Array(&d->oldText); |
194 | deinit_Array(&d->text); | 349 | deinit_Array(&d->text); |
195 | } | 350 | } |
@@ -199,6 +354,11 @@ void setFont_InputWidget(iInputWidget *d, int fontId) { | |||
199 | updateMetrics_InputWidget_(d); | 354 | updateMetrics_InputWidget_(d); |
200 | } | 355 | } |
201 | 356 | ||
357 | static const iInputLine *line_InputWidget_(const iInputWidget *d, size_t index) { | ||
358 | iAssert(!isEmpty_Array(&d->lines)); | ||
359 | return constAt_Array(&d->lines, index); | ||
360 | } | ||
361 | |||
202 | static void pushUndo_InputWidget_(iInputWidget *d) { | 362 | static void pushUndo_InputWidget_(iInputWidget *d) { |
203 | iInputUndo undo; | 363 | iInputUndo undo; |
204 | init_InputUndo_(&undo, &d->text, d->cursor); | 364 | init_InputUndo_(&undo, &d->text, d->cursor); |
@@ -241,10 +401,6 @@ static const iString *omitDefaultScheme_(iString *url) { | |||
241 | return url; | 401 | return url; |
242 | } | 402 | } |
243 | 403 | ||
244 | static iString *utf32toUtf8_InputWidget_(const iInputWidget *d) { | ||
245 | return newUnicodeN_String(constData_Array(&d->text), size_Array(&d->text)); | ||
246 | } | ||
247 | |||
248 | const iString *text_InputWidget(const iInputWidget *d) { | 404 | const iString *text_InputWidget(const iInputWidget *d) { |
249 | if (d) { | 405 | if (d) { |
250 | iString *text = collect_String(utf32toUtf8_InputWidget_(d)); | 406 | iString *text = collect_String(utf32toUtf8_InputWidget_(d)); |
@@ -267,6 +423,20 @@ void setMaxLen_InputWidget(iInputWidget *d, size_t maxLen) { | |||
267 | updateSizeForFixedLength_InputWidget_(d); | 423 | updateSizeForFixedLength_InputWidget_(d); |
268 | } | 424 | } |
269 | 425 | ||
426 | void setMaxLayoutLines_InputWidget(iInputWidget *d, size_t maxLayoutLines) { | ||
427 | d->maxLayoutLines = maxLayoutLines; | ||
428 | updateMetrics_InputWidget_(d); | ||
429 | } | ||
430 | |||
431 | void setValidator_InputWidget(iInputWidget *d, iInputWidgetValidatorFunc validator, void *context) { | ||
432 | d->validator = validator; | ||
433 | d->validatorContext = context; | ||
434 | } | ||
435 | |||
436 | void setEnterKeyEnabled_InputWidget(iInputWidget *d, iBool enterKeyEnabled) { | ||
437 | iChangeFlags(d->inFlags, enterKeyEnabled_InputWidgetFlag, enterKeyEnabled); | ||
438 | } | ||
439 | |||
270 | void setHint_InputWidget(iInputWidget *d, const char *hintText) { | 440 | void setHint_InputWidget(iInputWidget *d, const char *hintText) { |
271 | /* Keep original for retranslations. */ | 441 | /* Keep original for retranslations. */ |
272 | setCStr_String(&d->srcHint, hintText); | 442 | setCStr_String(&d->srcHint, hintText); |
@@ -285,27 +455,12 @@ void setContentPadding_InputWidget(iInputWidget *d, int left, int right) { | |||
285 | refresh_Widget(d); | 455 | refresh_Widget(d); |
286 | } | 456 | } |
287 | 457 | ||
288 | static const iChar sensitiveChar_ = 0x25cf; /* black circle */ | ||
289 | |||
290 | static iString *visText_InputWidget_(const iInputWidget *d) { | ||
291 | iString *text; | ||
292 | if (~d->inFlags & isSensitive_InputWidgetFlag) { | ||
293 | text = newUnicodeN_String(constData_Array(&d->text), size_Array(&d->text)); | ||
294 | } | ||
295 | else { | ||
296 | text = new_String(); | ||
297 | for (size_t i = 0; i < size_Array(&d->text); ++i) { | ||
298 | appendChar_String(text, sensitiveChar_); | ||
299 | } | ||
300 | } | ||
301 | return text; | ||
302 | } | ||
303 | |||
304 | static void updateBuffered_InputWidget_(iInputWidget *d) { | 458 | static void updateBuffered_InputWidget_(iInputWidget *d) { |
305 | iWindow *win = get_Window(); | ||
306 | invalidateBuffered_InputWidget_(d); | 459 | invalidateBuffered_InputWidget_(d); |
307 | iString *bufText = NULL; | 460 | iString *bufText = NULL; |
461 | #if 0 | ||
308 | if (d->inFlags & isUrl_InputWidgetFlag && as_Widget(d)->root == win->keyRoot) { | 462 | if (d->inFlags & isUrl_InputWidgetFlag && as_Widget(d)->root == win->keyRoot) { |
463 | /* TODO: Move this omitting to `updateLines_`? */ | ||
309 | /* Highlight the host name. */ | 464 | /* Highlight the host name. */ |
310 | iUrl parts; | 465 | iUrl parts; |
311 | const iString *text = collect_String(utf32toUtf8_InputWidget_(d)); | 466 | const iString *text = collect_String(utf32toUtf8_InputWidget_(d)); |
@@ -319,6 +474,7 @@ static void updateBuffered_InputWidget_(iInputWidget *d) { | |||
319 | appendRange_String(bufText, (iRangecc){ parts.host.end, constEnd_String(text) }); | 474 | appendRange_String(bufText, (iRangecc){ parts.host.end, constEnd_String(text) }); |
320 | } | 475 | } |
321 | } | 476 | } |
477 | #endif | ||
322 | if (!bufText) { | 478 | if (!bufText) { |
323 | bufText = visText_InputWidget_(d); | 479 | bufText = visText_InputWidget_(d); |
324 | } | 480 | } |
@@ -350,7 +506,7 @@ void setText_InputWidget(iInputWidget *d, const iString *text) { | |||
350 | } | 506 | } |
351 | if (isFocused_Widget(d)) { | 507 | if (isFocused_Widget(d)) { |
352 | d->cursor = size_Array(&d->text); | 508 | d->cursor = size_Array(&d->text); |
353 | selectAll_InputWidget(d); | 509 | // selectAll_InputWidget(d); |
354 | } | 510 | } |
355 | else { | 511 | else { |
356 | d->cursor = iMin(d->cursor, size_Array(&d->text)); | 512 | d->cursor = iMin(d->cursor, size_Array(&d->text)); |
@@ -359,6 +515,7 @@ void setText_InputWidget(iInputWidget *d, const iString *text) { | |||
359 | if (!isFocused_Widget(d)) { | 515 | if (!isFocused_Widget(d)) { |
360 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | 516 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; |
361 | } | 517 | } |
518 | updateLinesAndResize_InputWidget_(d); | ||
362 | refresh_Widget(as_Widget(d)); | 519 | refresh_Widget(as_Widget(d)); |
363 | } | 520 | } |
364 | 521 | ||
@@ -404,8 +561,9 @@ void begin_InputWidget(iInputWidget *d) { | |||
404 | else { | 561 | else { |
405 | d->cursor = iMin(size_Array(&d->text), d->maxLen - 1); | 562 | d->cursor = iMin(size_Array(&d->text), d->maxLen - 1); |
406 | } | 563 | } |
564 | updateCursorLine_InputWidget_(d); | ||
407 | SDL_StartTextInput(); | 565 | SDL_StartTextInput(); |
408 | setFlags_Widget(w, selected_WidgetFlag, iTrue); | 566 | setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iTrue); |
409 | showCursor_InputWidget_(d); | 567 | showCursor_InputWidget_(d); |
410 | refresh_Widget(w); | 568 | refresh_Widget(w); |
411 | d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); | 569 | d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); |
@@ -433,9 +591,10 @@ void end_InputWidget(iInputWidget *d, iBool accept) { | |||
433 | SDL_RemoveTimer(d->timer); | 591 | SDL_RemoveTimer(d->timer); |
434 | d->timer = 0; | 592 | d->timer = 0; |
435 | SDL_StopTextInput(); | 593 | SDL_StopTextInput(); |
436 | setFlags_Widget(w, selected_WidgetFlag, iFalse); | 594 | setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iFalse); |
437 | const char *id = cstr_String(id_Widget(as_Widget(d))); | 595 | const char *id = cstr_String(id_Widget(as_Widget(d))); |
438 | if (!*id) id = "_"; | 596 | if (!*id) id = "_"; |
597 | updateLinesAndResize_InputWidget_(d); | ||
439 | refresh_Widget(w); | 598 | refresh_Widget(w); |
440 | postCommand_Widget(w, | 599 | postCommand_Widget(w, |
441 | "input.ended id:%s enter:%d arg:%d", | 600 | "input.ended id:%s enter:%d arg:%d", |
@@ -476,7 +635,6 @@ iLocalDef iBool isMarking_(void) { | |||
476 | } | 635 | } |
477 | 636 | ||
478 | void setCursor_InputWidget(iInputWidget *d, size_t pos) { | 637 | void setCursor_InputWidget(iInputWidget *d, size_t pos) { |
479 | showCursor_InputWidget_(d); | ||
480 | if (isEmpty_Array(&d->text)) { | 638 | if (isEmpty_Array(&d->text)) { |
481 | d->cursor = 0; | 639 | d->cursor = 0; |
482 | } | 640 | } |
@@ -496,6 +654,51 @@ void setCursor_InputWidget(iInputWidget *d, size_t pos) { | |||
496 | else { | 654 | else { |
497 | iZap(d->mark); | 655 | iZap(d->mark); |
498 | } | 656 | } |
657 | showCursor_InputWidget_(d); | ||
658 | } | ||
659 | |||
660 | static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { | ||
661 | if (x <= 0) { | ||
662 | return line->offset; | ||
663 | } | ||
664 | const char *endPos; | ||
665 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); | ||
666 | size_t index = line->offset; | ||
667 | if (endPos == constEnd_String(&line->text)) { | ||
668 | index += length_String(&line->text); | ||
669 | } | ||
670 | else { | ||
671 | /* Need to know the actual character index. */ | ||
672 | /* TODO: tryAdvance could tell us this directly with an extra return value */ | ||
673 | iConstForEach(String, i, &line->text) { | ||
674 | if (i.pos >= endPos) break; | ||
675 | index++; | ||
676 | } | ||
677 | } | ||
678 | return index; | ||
679 | } | ||
680 | |||
681 | static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) { | ||
682 | const iInputLine *line = line_InputWidget_(d, d->cursorLine); | ||
683 | int xPos = advanceN_Text(d->font, cstr_String(&line->text), d->cursor - line->offset).x; | ||
684 | size_t newCursor = iInvalidPos; | ||
685 | const size_t numLines = size_Array(&d->lines); | ||
686 | if (dir < 0 && d->cursorLine > 0) { | ||
687 | newCursor = indexForRelativeX_InputWidget_(d, xPos, --line); | ||
688 | } | ||
689 | else if (dir > 0 && d->cursorLine < numLines - 1) { | ||
690 | newCursor = indexForRelativeX_InputWidget_(d, xPos, ++line); | ||
691 | } | ||
692 | if (newCursor != iInvalidPos) { | ||
693 | /* Clamp it to the current line. */ | ||
694 | newCursor = iMax(newCursor, line->offset); | ||
695 | newCursor = iMin(newCursor, line->offset + length_String(&line->text) - | ||
696 | /* last line is allowed to go to the cursorMax */ | ||
697 | ((const void *) line < constAt_Array(&d->lines, numLines - 1) ? 1 : 0)); | ||
698 | setCursor_InputWidget(d, newCursor); | ||
699 | return iTrue; | ||
700 | } | ||
701 | return iFalse; | ||
499 | } | 702 | } |
500 | 703 | ||
501 | void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) { | 704 | void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) { |
@@ -526,6 +729,10 @@ static iRanges mark_InputWidget_(const iInputWidget *d) { | |||
526 | } | 729 | } |
527 | 730 | ||
528 | static void contentsWereChanged_InputWidget_(iInputWidget *d) { | 731 | static void contentsWereChanged_InputWidget_(iInputWidget *d) { |
732 | if (d->validator) { | ||
733 | d->validator(d, d->validatorContext); /* this may change the contents */ | ||
734 | } | ||
735 | updateLinesAndResize_InputWidget_(d); | ||
529 | if (d->inFlags & notifyEdits_InputWidgetFlag) { | 736 | if (d->inFlags & notifyEdits_InputWidgetFlag) { |
530 | postCommand_Widget(d, "input.edited id:%s", cstr_String(id_Widget(constAs_Widget(d)))); | 737 | postCommand_Widget(d, "input.edited id:%s", cstr_String(id_Widget(constAs_Widget(d)))); |
531 | } | 738 | } |
@@ -591,21 +798,18 @@ static size_t skipWord_InputWidget_(const iInputWidget *d, size_t pos, int dir) | |||
591 | return pos; | 798 | return pos; |
592 | } | 799 | } |
593 | 800 | ||
594 | iLocalDef iInt2 padding_(void) { | 801 | #if 0 |
595 | return init_I2(gap_UI / 2, gap_UI / 2); | 802 | static iInt2 textOrigin_InputWidget_(const iInputWidget *d) { //}, const char *visText) { |
596 | } | 803 | // const iWidget *w = constAs_Widget(d); |
597 | 804 | iRect bounds = contentBounds_InputWidget_(d);/* adjusted_Rect(bounds_Widget(w), | |
598 | static iInt2 textOrigin_InputWidget_(const iInputWidget *d, const char *visText) { | ||
599 | const iWidget *w = constAs_Widget(d); | ||
600 | iRect bounds = adjusted_Rect(bounds_Widget(w), | ||
601 | addX_I2(padding_(), d->leftPadding), | 805 | addX_I2(padding_(), d->leftPadding), |
602 | neg_I2(addX_I2(padding_(), d->rightPadding))); | 806 | neg_I2(addX_I2(padding_(), d->rightPadding)));*/ |
603 | const iInt2 emSize = advance_Text(d->font, "M"); | 807 | // const iInt2 emSize = advance_Text(d->font, "M"); |
604 | const int textWidth = advance_Text(d->font, visText).x; | 808 | // const int textWidth = advance_Text(d->font, visText).x; |
605 | const int cursorX = advanceN_Text(d->font, visText, d->cursor).x; | 809 | // const int cursorX = advanceN_Text(d->font, visText, d->cursor).x; |
606 | int xOff = 0; | 810 | // int xOff = 0; |
607 | shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); | 811 | // shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); |
608 | if (d->maxLen == 0) { | 812 | /* if (d->maxLen == 0) { |
609 | if (textWidth > width_Rect(bounds) - emSize.x) { | 813 | if (textWidth > width_Rect(bounds) - emSize.x) { |
610 | xOff = width_Rect(bounds) - emSize.x - textWidth; | 814 | xOff = width_Rect(bounds) - emSize.x - textWidth; |
611 | } | 815 | } |
@@ -613,32 +817,20 @@ static iInt2 textOrigin_InputWidget_(const iInputWidget *d, const char *visText) | |||
613 | xOff = width_Rect(bounds) / 2 - cursorX; | 817 | xOff = width_Rect(bounds) / 2 - cursorX; |
614 | } | 818 | } |
615 | xOff = iMin(xOff, 0); | 819 | xOff = iMin(xOff, 0); |
616 | } | 820 | }*/ |
617 | const int yOff = (height_Rect(bounds) - lineHeight_Text(d->font)) / 2; | 821 | // const int yOff = 0.3f * lineHeight_Text(d->font); // (height_Rect(bounds) - lineHeight_Text(d->font)) / 2; |
618 | return add_I2(topLeft_Rect(bounds), init_I2(xOff, yOff)); | 822 | // return addY_I2(topLeft_Rect(bounds), yOff); |
823 | |||
619 | } | 824 | } |
825 | #endif | ||
620 | 826 | ||
621 | static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { | 827 | static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { |
622 | iString *visText = visText_InputWidget_(d); | 828 | const iInt2 pos = sub_I2(coord, contentBounds_InputWidget_(d).pos); |
623 | iInt2 pos = sub_I2(coord, textOrigin_InputWidget_(d, cstr_String(visText))); | 829 | const size_t lineNumber = iMin(pos.y / lineHeight_Text(d->font), (int) size_Array(&d->lines) - 1); |
624 | size_t index = 0; | 830 | const iInputLine *line = line_InputWidget_(d, lineNumber); |
625 | if (pos.x > 0) { | 831 | const char *endPos; |
626 | const char *endPos; | 832 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), pos.x, &endPos); |
627 | tryAdvanceNoWrap_Text(d->font, range_String(visText), pos.x, &endPos); | 833 | return indexForRelativeX_InputWidget_(d, pos.x, line); |
628 | if (endPos == constEnd_String(visText)) { | ||
629 | index = cursorMax_InputWidget_(d); | ||
630 | } | ||
631 | else { | ||
632 | /* Need to know the actual character index. */ | ||
633 | /* TODO: tryAdvance could tell us this directly with an extra return value */ | ||
634 | iConstForEach(String, i, visText) { | ||
635 | if (i.pos >= endPos) break; | ||
636 | index++; | ||
637 | } | ||
638 | } | ||
639 | } | ||
640 | delete_String(visText); | ||
641 | return index; | ||
642 | } | 834 | } |
643 | 835 | ||
644 | static iBool copy_InputWidget_(iInputWidget *d, iBool doCut) { | 836 | static iBool copy_InputWidget_(iInputWidget *d, iBool doCut) { |
@@ -683,6 +875,14 @@ static iChar at_InputWidget_(const iInputWidget *d, size_t pos) { | |||
683 | return *(const iChar *) constAt_Array(&d->text, pos); | 875 | return *(const iChar *) constAt_Array(&d->text, pos); |
684 | } | 876 | } |
685 | 877 | ||
878 | static iRanges lineRange_InputWidget_(const iInputWidget *d) { | ||
879 | if (isEmpty_Array(&d->lines)) { | ||
880 | return (iRanges){ 0, 0 }; | ||
881 | } | ||
882 | const iInputLine *line = line_InputWidget_(d, d->cursorLine); | ||
883 | return (iRanges){ line->offset, line->offset + length_String(&line->text) }; | ||
884 | } | ||
885 | |||
686 | static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { | 886 | static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { |
687 | const size_t textLen = size_Array(&d->text); | 887 | const size_t textLen = size_Array(&d->text); |
688 | if (dir < 0 && *pos > 0) { | 888 | if (dir < 0 && *pos > 0) { |
@@ -700,6 +900,20 @@ static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { | |||
700 | } | 900 | } |
701 | } | 901 | } |
702 | 902 | ||
903 | static iRect bounds_InputWidget_(const iInputWidget *d) { | ||
904 | const iWidget *w = constAs_Widget(d); | ||
905 | iRect bounds = bounds_Widget(w); | ||
906 | if (!isFocused_Widget(d)) { | ||
907 | return bounds; | ||
908 | } | ||
909 | bounds.size.y = contentHeight_InputWidget_(d, iFalse) + 3 * padding_().y; | ||
910 | return bounds; | ||
911 | } | ||
912 | |||
913 | static iBool contains_InputWidget_(const iInputWidget *d, iInt2 coord) { | ||
914 | return contains_Rect(bounds_InputWidget_(d), coord); | ||
915 | } | ||
916 | |||
703 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 917 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { |
704 | iWidget *w = as_Widget(d); | 918 | iWidget *w = as_Widget(d); |
705 | if (isCommand_Widget(w, ev, "focus.gained")) { | 919 | if (isCommand_Widget(w, ev, "focus.gained")) { |
@@ -746,19 +960,22 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
746 | } | 960 | } |
747 | else if (isMetricsChange_UserEvent(ev)) { | 961 | else if (isMetricsChange_UserEvent(ev)) { |
748 | updateMetrics_InputWidget_(d); | 962 | updateMetrics_InputWidget_(d); |
963 | updateLinesAndResize_InputWidget_(d); | ||
749 | } | 964 | } |
750 | else if (isResize_UserEvent(ev)) { | 965 | else if (isResize_UserEvent(ev)) { |
751 | if (d->inFlags & isUrl_InputWidgetFlag) { | 966 | if (d->inFlags & isUrl_InputWidgetFlag) { |
752 | /* Restore/omit the default scheme if necessary. */ | 967 | /* Restore/omit the default scheme if necessary. */ |
753 | setText_InputWidget(d, text_InputWidget(d)); | 968 | setText_InputWidget(d, text_InputWidget(d)); |
754 | } | 969 | } |
970 | updateLinesAndResize_InputWidget_(d); | ||
755 | } | 971 | } |
756 | else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { | 972 | else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { |
757 | copy_InputWidget_(d, iFalse); | 973 | copy_InputWidget_(d, iFalse); |
758 | return iTrue; | 974 | return iTrue; |
759 | } | 975 | } |
760 | if (ev->type == SDL_MOUSEMOTION && isHover_Widget(d)) { | 976 | if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { |
761 | const iInt2 inner = windowToInner_Widget(w, init_I2(ev->motion.x, ev->motion.y)); | 977 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); |
978 | const iInt2 inner = windowToInner_Widget(w, coord); | ||
762 | setCursor_Window(get_Window(), | 979 | setCursor_Window(get_Window(), |
763 | inner.x >= 2 * gap_UI + d->leftPadding && | 980 | inner.x >= 2 * gap_UI + d->leftPadding && |
764 | inner.x < width_Widget(w) - d->rightPadding | 981 | inner.x < width_Widget(w) - d->rightPadding |
@@ -768,30 +985,38 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
768 | switch (processEvent_Click(&d->click, ev)) { | 985 | switch (processEvent_Click(&d->click, ev)) { |
769 | case none_ClickResult: | 986 | case none_ClickResult: |
770 | break; | 987 | break; |
771 | case started_ClickResult: | 988 | case started_ClickResult: { |
772 | setFocus_Widget(w); | 989 | setFocus_Widget(w); |
990 | const size_t oldCursor = d->cursor; | ||
773 | setCursor_InputWidget(d, coordIndex_InputWidget_(d, pos_Click(&d->click))); | 991 | setCursor_InputWidget(d, coordIndex_InputWidget_(d, pos_Click(&d->click))); |
774 | iZap(d->mark); | 992 | if (keyMods_Sym(modState_Keys()) == KMOD_SHIFT) { |
775 | iZap(d->initialMark); | 993 | d->mark = d->initialMark = (iRanges){ oldCursor, d->cursor }; |
776 | d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag); | 994 | d->inFlags |= isMarking_InputWidgetFlag; |
777 | if (d->click.count == 2) { | ||
778 | d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag; | ||
779 | d->mark.start = d->mark.end = d->cursor; | ||
780 | extendRange_InputWidget_(d, &d->mark.start, -1); | ||
781 | extendRange_InputWidget_(d, &d->mark.end, +1); | ||
782 | d->initialMark = d->mark; | ||
783 | refresh_Widget(w); | ||
784 | } | 995 | } |
785 | if (d->click.count == 3) { | 996 | else { |
786 | selectAll_InputWidget(d); | 997 | iZap(d->mark); |
998 | iZap(d->initialMark); | ||
999 | d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag); | ||
1000 | if (d->click.count == 2) { | ||
1001 | d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag; | ||
1002 | d->mark.start = d->mark.end = d->cursor; | ||
1003 | extendRange_InputWidget_(d, &d->mark.start, -1); | ||
1004 | extendRange_InputWidget_(d, &d->mark.end, +1); | ||
1005 | d->initialMark = d->mark; | ||
1006 | refresh_Widget(w); | ||
1007 | } | ||
1008 | if (d->click.count == 3) { | ||
1009 | selectAll_InputWidget(d); | ||
1010 | } | ||
787 | } | 1011 | } |
788 | return iTrue; | 1012 | return iTrue; |
1013 | } | ||
789 | case aborted_ClickResult: | 1014 | case aborted_ClickResult: |
790 | d->inFlags &= ~isMarking_InputWidgetFlag; | 1015 | d->inFlags &= ~isMarking_InputWidgetFlag; |
791 | return iTrue; | 1016 | return iTrue; |
792 | case drag_ClickResult: | 1017 | case drag_ClickResult: |
793 | showCursor_InputWidget_(d); | ||
794 | d->cursor = coordIndex_InputWidget_(d, pos_Click(&d->click)); | 1018 | d->cursor = coordIndex_InputWidget_(d, pos_Click(&d->click)); |
1019 | showCursor_InputWidget_(d); | ||
795 | if (~d->inFlags & isMarking_InputWidgetFlag) { | 1020 | if (~d->inFlags & isMarking_InputWidgetFlag) { |
796 | d->inFlags |= isMarking_InputWidgetFlag; | 1021 | d->inFlags |= isMarking_InputWidgetFlag; |
797 | d->mark.start = d->cursor; | 1022 | d->mark.start = d->cursor; |
@@ -808,6 +1033,12 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
808 | d->inFlags &= ~isMarking_InputWidgetFlag; | 1033 | d->inFlags &= ~isMarking_InputWidgetFlag; |
809 | return iTrue; | 1034 | return iTrue; |
810 | } | 1035 | } |
1036 | if (ev->type == SDL_MOUSEMOTION && flags_Widget(w) & keepOnTop_WidgetFlag) { | ||
1037 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); | ||
1038 | if (contains_Click(&d->click, coord)) { | ||
1039 | return iTrue; | ||
1040 | } | ||
1041 | } | ||
811 | if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && | 1042 | if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && |
812 | contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { | 1043 | contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { |
813 | iWidget *clipMenu = findWidget_App("clipmenu"); | 1044 | iWidget *clipMenu = findWidget_App("clipmenu"); |
@@ -822,7 +1053,10 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
822 | if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { | 1053 | if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { |
823 | return iTrue; | 1054 | return iTrue; |
824 | } | 1055 | } |
825 | const size_t curMax = cursorMax_InputWidget_(d); | 1056 | const size_t curMax = cursorMax_InputWidget_(d); |
1057 | const iRanges lineRange = lineRange_InputWidget_(d); | ||
1058 | const size_t lineFirst = lineRange.start; | ||
1059 | const size_t lineLast = lineRange.end == curMax ? curMax : iMax(lineRange.start, lineRange.end - 1); | ||
826 | if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) { | 1060 | if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) { |
827 | const int key = ev->key.keysym.sym; | 1061 | const int key = ev->key.keysym.sym; |
828 | const int mods = keyMods_Sym(ev->key.keysym.mod); | 1062 | const int mods = keyMods_Sym(ev->key.keysym.mod); |
@@ -852,8 +1086,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
852 | return iTrue; | 1086 | return iTrue; |
853 | case SDLK_RETURN: | 1087 | case SDLK_RETURN: |
854 | case SDLK_KP_ENTER: | 1088 | case SDLK_KP_ENTER: |
855 | d->inFlags |= enterPressed_InputWidgetFlag; | 1089 | if (mods == KMOD_SHIFT) { |
856 | setFocus_Widget(NULL); | 1090 | pushUndo_InputWidget_(d); |
1091 | deleteMarked_InputWidget_(d); | ||
1092 | insertChar_InputWidget_(d, '\n'); | ||
1093 | contentsWereChanged_InputWidget_(d); | ||
1094 | return iTrue; | ||
1095 | } | ||
1096 | if (d->inFlags & enterKeyEnabled_InputWidgetFlag) { | ||
1097 | d->inFlags |= enterPressed_InputWidgetFlag; | ||
1098 | setFocus_Widget(NULL); | ||
1099 | } | ||
857 | return iTrue; | 1100 | return iTrue; |
858 | case SDLK_ESCAPE: | 1101 | case SDLK_ESCAPE: |
859 | end_InputWidget(d, iFalse); | 1102 | end_InputWidget(d, iFalse); |
@@ -927,7 +1170,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
927 | break; | 1170 | break; |
928 | case SDLK_HOME: | 1171 | case SDLK_HOME: |
929 | case SDLK_END: | 1172 | case SDLK_END: |
930 | setCursor_InputWidget(d, key == SDLK_HOME ? 0 : curMax); | 1173 | setCursor_InputWidget(d, key == SDLK_HOME ? lineFirst : lineLast); |
931 | refresh_Widget(w); | 1174 | refresh_Widget(w); |
932 | return iTrue; | 1175 | return iTrue; |
933 | case SDLK_a: | 1176 | case SDLK_a: |
@@ -944,7 +1187,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
944 | /* fall through for Emacs-style Home/End */ | 1187 | /* fall through for Emacs-style Home/End */ |
945 | case SDLK_e: | 1188 | case SDLK_e: |
946 | if (mods == KMOD_CTRL || mods == (KMOD_CTRL | KMOD_SHIFT)) { | 1189 | if (mods == KMOD_CTRL || mods == (KMOD_CTRL | KMOD_SHIFT)) { |
947 | setCursor_InputWidget(d, key == 'a' ? 0 : curMax); | 1190 | setCursor_InputWidget(d, key == 'a' ? lineFirst : lineLast); |
948 | refresh_Widget(w); | 1191 | refresh_Widget(w); |
949 | return iTrue; | 1192 | return iTrue; |
950 | } | 1193 | } |
@@ -953,7 +1196,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
953 | case SDLK_RIGHT: { | 1196 | case SDLK_RIGHT: { |
954 | const int dir = (key == SDLK_LEFT ? -1 : +1); | 1197 | const int dir = (key == SDLK_LEFT ? -1 : +1); |
955 | if (mods & byLine_KeyModifier) { | 1198 | if (mods & byLine_KeyModifier) { |
956 | setCursor_InputWidget(d, dir < 0 ? 0 : curMax); | 1199 | setCursor_InputWidget(d, dir < 0 ? lineFirst : lineLast); |
957 | } | 1200 | } |
958 | else if (mods & byWord_KeyModifier) { | 1201 | else if (mods & byWord_KeyModifier) { |
959 | setCursor_InputWidget(d, skipWord_InputWidget_(d, d->cursor, dir)); | 1202 | setCursor_InputWidget(d, skipWord_InputWidget_(d, d->cursor, dir)); |
@@ -970,9 +1213,22 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
970 | return iTrue; | 1213 | return iTrue; |
971 | } | 1214 | } |
972 | case SDLK_TAB: | 1215 | case SDLK_TAB: |
973 | case SDLK_DOWN: /* for moving to lookup from url entry */ | ||
974 | /* Allow focus switching. */ | 1216 | /* Allow focus switching. */ |
975 | return processEvent_Widget(as_Widget(d), ev); | 1217 | return processEvent_Widget(as_Widget(d), ev); |
1218 | case SDLK_UP: | ||
1219 | if (moveCursorByLine_InputWidget_(d, -1)) { | ||
1220 | refresh_Widget(d); | ||
1221 | return iTrue; | ||
1222 | } | ||
1223 | /* For moving to lookup from url entry. */ | ||
1224 | return processEvent_Widget(as_Widget(d), ev); | ||
1225 | case SDLK_DOWN: | ||
1226 | if (moveCursorByLine_InputWidget_(d, +1)) { | ||
1227 | refresh_Widget(d); | ||
1228 | return iTrue; | ||
1229 | } | ||
1230 | /* For moving to lookup from url entry. */ | ||
1231 | return processEvent_Widget(as_Widget(d), ev); | ||
976 | } | 1232 | } |
977 | if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { | 1233 | if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { |
978 | return iFalse; | 1234 | return iFalse; |
@@ -1003,21 +1259,23 @@ static iBool isWhite_(const iString *str) { | |||
1003 | 1259 | ||
1004 | static void draw_InputWidget_(const iInputWidget *d) { | 1260 | static void draw_InputWidget_(const iInputWidget *d) { |
1005 | const iWidget *w = constAs_Widget(d); | 1261 | const iWidget *w = constAs_Widget(d); |
1006 | iRect bounds = adjusted_Rect(bounds_Widget(w), padding_(), neg_I2(padding_())); | 1262 | iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); |
1007 | iBool isHint = iFalse; | 1263 | iBool isHint = iFalse; |
1008 | const iBool isFocused = isFocused_Widget(w); | 1264 | const iBool isFocused = isFocused_Widget(w); |
1009 | const iBool isHover = isHover_Widget(w) && | 1265 | const iBool isHover = isHover_Widget(w) && |
1010 | contains_Widget(w, mouseCoord_Window(get_Window())); | 1266 | contains_InputWidget_(d, mouseCoord_Window(get_Window())); |
1011 | if (d->inFlags & needUpdateBuffer_InputWidgetFlag) { | 1267 | if (d->inFlags & needUpdateBuffer_InputWidgetFlag) { |
1012 | updateBuffered_InputWidget_(iConstCast(iInputWidget *, d)); | 1268 | updateBuffered_InputWidget_(iConstCast(iInputWidget *, d)); |
1013 | } | 1269 | } |
1014 | iPaint p; | 1270 | iPaint p; |
1015 | init_Paint(&p); | 1271 | init_Paint(&p); |
1016 | iString *text = visText_InputWidget_(d); | 1272 | /* `lines` is already up to date and ready for drawing. */ |
1017 | if (isWhite_(text) && !isEmpty_String(&d->hint)) { | 1273 | /* TODO: If empty, draw the hint. */ |
1018 | set_String(text, &d->hint); | 1274 | // iString *text = visText_InputWidget_(d); |
1019 | isHint = iTrue; | 1275 | // if (isWhite_(text) && !isEmpty_String(&d->hint)) { |
1020 | } | 1276 | // set_String(text, &d->hint); |
1277 | // isHint = iTrue; | ||
1278 | // } | ||
1021 | fillRect_Paint( | 1279 | fillRect_Paint( |
1022 | &p, bounds, isFocused ? uiInputBackgroundFocused_ColorId : uiInputBackground_ColorId); | 1280 | &p, bounds, isFocused ? uiInputBackgroundFocused_ColorId : uiInputBackground_ColorId); |
1023 | drawRectThickness_Paint(&p, | 1281 | drawRectThickness_Paint(&p, |
@@ -1026,29 +1284,53 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1026 | isFocused ? uiInputFrameFocused_ColorId | 1284 | isFocused ? uiInputFrameFocused_ColorId |
1027 | : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); | 1285 | : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); |
1028 | setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), init_I2(-d->rightPadding, 0))); | 1286 | setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), init_I2(-d->rightPadding, 0))); |
1029 | const iInt2 textOrigin = textOrigin_InputWidget_(d, cstr_String(text)); | 1287 | const iRect contentBounds = contentBounds_InputWidget_(d); |
1030 | if (isFocused && !isEmpty_Range(&d->mark)) { | 1288 | // const iInt2 textOrigin = textOrigin_InputWidget_(d); //, cstr_String(text)); |
1031 | /* Draw the selected range. */ | 1289 | iInt2 drawPos = topLeft_Rect(contentBounds); |
1032 | const int m1 = advanceN_Text(d->font, cstr_String(text), d->mark.start).x; | 1290 | const int fg = isHint ? uiAnnotation_ColorId |
1033 | const int m2 = advanceN_Text(d->font, cstr_String(text), d->mark.end).x; | 1291 | : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId |
1034 | fillRect_Paint(&p, | 1292 | : uiInputText_ColorId; |
1035 | (iRect){ addX_I2(textOrigin, iMin(m1, m2)), | 1293 | /* TODO: If buffered, just draw the buffered copy. */ |
1036 | init_I2(iAbs(m2 - m1), lineHeight_Text(d->font)) }, | 1294 | iConstForEach(Array, i, &d->lines) { |
1037 | uiMarked_ColorId); | 1295 | const iInputLine *line = i.value; |
1038 | } | 1296 | const iBool isLast = index_ArrayConstIterator(&i) == size_Array(&d->lines) - 1; |
1039 | if (d->buffered && !isFocused && !isHint) { | 1297 | const iInputLine *nextLine = isLast ? NULL : (line + 1); |
1040 | /* Most input widgets will use this, since only one is focused at a time. */ | 1298 | const iRanges lineRange = { line->offset, |
1041 | draw_TextBuf(d->buffered, textOrigin, white_ColorId); | 1299 | nextLine ? nextLine->offset : size_Array(&d->text) }; |
1042 | } | 1300 | if (isFocused && !isEmpty_Range(&d->mark)) { |
1043 | else { | 1301 | /* Draw the selected range. */ |
1044 | draw_Text(d->font, | 1302 | const iRanges mark = mark_InputWidget_(d); |
1045 | textOrigin, | 1303 | if (mark.start < lineRange.end && mark.end > lineRange.start) { |
1046 | isHint ? uiAnnotation_ColorId | 1304 | const int m1 = advanceN_Text(d->font, |
1047 | : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId | 1305 | cstr_String(&line->text), |
1048 | : uiInputText_ColorId, | 1306 | iMax(lineRange.start, mark.start) - line->offset) |
1049 | "%s", | 1307 | .x; |
1050 | cstr_String(text)); | 1308 | const int m2 = advanceN_Text(d->font, |
1051 | } | 1309 | cstr_String(&line->text), |
1310 | iMin(lineRange.end, mark.end) - line->offset) | ||
1311 | .x; | ||
1312 | fillRect_Paint(&p, | ||
1313 | (iRect){ addX_I2(drawPos, iMin(m1, m2)), | ||
1314 | init_I2(iAbs(m2 - m1), lineHeight_Text(d->font)) }, | ||
1315 | uiMarked_ColorId); | ||
1316 | } | ||
1317 | } | ||
1318 | drawRange_Text(d->font, drawPos, fg, range_String(&line->text)); | ||
1319 | drawPos.y += lineHeight_Text(d->font); | ||
1320 | } | ||
1321 | // if (d->buffered && !isFocused && !isHint) { | ||
1322 | // /* Most input widgets will use this, since only one is focused at a time. */ | ||
1323 | // draw_TextBuf(d->buffered, textOrigin, white_ColorId); | ||
1324 | // } | ||
1325 | // else { | ||
1326 | // draw_Text(d->font, | ||
1327 | // textOrigin, | ||
1328 | // isHint ? uiAnnotation_ColorId | ||
1329 | // : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId | ||
1330 | // : uiInputText_ColorId, | ||
1331 | // "%s", | ||
1332 | // cstr_String(text)); | ||
1333 | // } | ||
1052 | unsetClip_Paint(&p); | 1334 | unsetClip_Paint(&p); |
1053 | /* Cursor blinking. */ | 1335 | /* Cursor blinking. */ |
1054 | if (isFocused && d->cursorVis) { | 1336 | if (isFocused && d->cursorVis) { |
@@ -1073,23 +1355,36 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1073 | /* Bar cursor. */ | 1355 | /* Bar cursor. */ |
1074 | curSize = init_I2(gap_UI / 2, lineHeight_Text(d->font)); | 1356 | curSize = init_I2(gap_UI / 2, lineHeight_Text(d->font)); |
1075 | } | 1357 | } |
1358 | const iInputLine *curLine = line_InputWidget_(d, d->cursorLine); | ||
1359 | const iString * text = &curLine->text; | ||
1076 | /* The `gap_UI` offsets below are a hack. They are used because for some reason the | 1360 | /* The `gap_UI` offsets below are a hack. They are used because for some reason the |
1077 | cursor rect and the glyph inside don't quite position like during `run_Text_()`. */ | 1361 | cursor rect and the glyph inside don't quite position like during `run_Text_()`. */ |
1078 | const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(text), d->cursor); | 1362 | const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(text), d->cursor - curLine->offset); |
1079 | const iInt2 curPos = addX_I2(textOrigin, prefixSize.x + | 1363 | const iInt2 curPos = addX_I2(addY_I2(contentBounds.pos, lineHeight_Text(d->font) * d->cursorLine), |
1364 | prefixSize.x + | ||
1080 | (d->mode == insert_InputMode ? -curSize.x / 2 : 0)); | 1365 | (d->mode == insert_InputMode ? -curSize.x / 2 : 0)); |
1081 | const iRect curRect = { curPos, curSize }; | 1366 | const iRect curRect = { curPos, curSize }; |
1082 | fillRect_Paint(&p, curRect, uiInputCursor_ColorId); | 1367 | fillRect_Paint(&p, curRect, uiInputCursor_ColorId); |
1083 | if (d->mode == overwrite_InputMode) { | 1368 | if (d->mode == overwrite_InputMode) { |
1084 | draw_Text(d->font, addX_I2(curPos, iMin(1, gap_UI / 8)), uiInputCursorText_ColorId, "%s", cstr_String(&cur)); | 1369 | draw_Text(d->font, |
1370 | addX_I2(curPos, iMin(1, gap_UI / 8)), | ||
1371 | uiInputCursorText_ColorId, | ||
1372 | "%s", | ||
1373 | cstr_String(&cur)); | ||
1085 | deinit_String(&cur); | 1374 | deinit_String(&cur); |
1086 | } | 1375 | } |
1087 | } | 1376 | } |
1088 | delete_String(text); | 1377 | // delete_String(text); |
1089 | drawChildren_Widget(w); | 1378 | drawChildren_Widget(w); |
1090 | } | 1379 | } |
1091 | 1380 | ||
1381 | //static void sizeChanged_InputWidget_(iInputWidget *d) { | ||
1382 | // printf("[InputWidget] %p: size changed, updating layout\n", d); | ||
1383 | // updateLinesAndResize_InputWidget_(d, iFalse); | ||
1384 | //} | ||
1385 | |||
1092 | iBeginDefineSubclass(InputWidget, Widget) | 1386 | iBeginDefineSubclass(InputWidget, Widget) |
1093 | .processEvent = (iAny *) processEvent_InputWidget_, | 1387 | .processEvent = (iAny *) processEvent_InputWidget_, |
1094 | .draw = (iAny *) draw_InputWidget_, | 1388 | .draw = (iAny *) draw_InputWidget_, |
1389 | // .sizeChanged = (iAny *) sizeChanged_InputWidget_, | ||
1095 | iEndDefineSubclass(InputWidget) | 1390 | iEndDefineSubclass(InputWidget) |
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h index 86484dcc..cb32a29c 100644 --- a/src/ui/inputwidget.h +++ b/src/ui/inputwidget.h | |||
@@ -39,6 +39,8 @@ struct Impl_InputWidgetContentPadding { | |||
39 | int right; | 39 | int right; |
40 | }; | 40 | }; |
41 | 41 | ||
42 | typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context); | ||
43 | |||
42 | void setHint_InputWidget (iInputWidget *, const char *hintText); | 44 | void setHint_InputWidget (iInputWidget *, const char *hintText); |
43 | void setMode_InputWidget (iInputWidget *, enum iInputMode mode); | 45 | void setMode_InputWidget (iInputWidget *, enum iInputMode mode); |
44 | void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); | 46 | void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); |
@@ -47,6 +49,9 @@ void setTextCStr_InputWidget (iInputWidget *, const char *cstr); | |||
47 | void setFont_InputWidget (iInputWidget *, int fontId); | 49 | void setFont_InputWidget (iInputWidget *, int fontId); |
48 | void setCursor_InputWidget (iInputWidget *, size_t pos); | 50 | void setCursor_InputWidget (iInputWidget *, size_t pos); |
49 | void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ | 51 | void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ |
52 | void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines); | ||
53 | void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); | ||
54 | void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); | ||
50 | void begin_InputWidget (iInputWidget *); | 55 | void begin_InputWidget (iInputWidget *); |
51 | void end_InputWidget (iInputWidget *, iBool accept); | 56 | void end_InputWidget (iInputWidget *, iBool accept); |
52 | void selectAll_InputWidget (iInputWidget *); | 57 | void selectAll_InputWidget (iInputWidget *); |
diff --git a/src/ui/root.c b/src/ui/root.c index 43fadbfa..76ef05c4 100644 --- a/src/ui/root.c +++ b/src/ui/root.c | |||
@@ -285,6 +285,16 @@ void destroyPending_Root(iRoot *d) { | |||
285 | setCurrent_Root(NULL); | 285 | setCurrent_Root(NULL); |
286 | } | 286 | } |
287 | 287 | ||
288 | void postArrange_Root(iRoot *d) { | ||
289 | if (!d->pendingArrange) { | ||
290 | d->pendingArrange = iTrue; | ||
291 | SDL_Event ev = { .type = SDL_USEREVENT }; | ||
292 | ev.user.code = arrange_UserEventCode; | ||
293 | ev.user.data2 = d; | ||
294 | SDL_PushEvent(&ev); | ||
295 | } | ||
296 | } | ||
297 | |||
288 | iPtrArray *onTop_Root(iRoot *d) { | 298 | iPtrArray *onTop_Root(iRoot *d) { |
289 | if (!d->onTop) { | 299 | if (!d->onTop) { |
290 | d->onTop = new_PtrArray(); | 300 | d->onTop = new_PtrArray(); |
@@ -337,6 +347,12 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
337 | setFocus_Widget(findWidget_App(cstr_Rangecc(range_Command(cmd, "id")))); | 347 | setFocus_Widget(findWidget_App(cstr_Rangecc(range_Command(cmd, "id")))); |
338 | return iTrue; | 348 | return iTrue; |
339 | } | 349 | } |
350 | else if (equal_Command(cmd, "input.resized")) { | ||
351 | /* No parent handled this, so do a full rearrangement. */ | ||
352 | arrange_Widget(root); | ||
353 | postRefresh_App(); | ||
354 | return iTrue; | ||
355 | } | ||
340 | else if (equal_Command(cmd, "window.focus.lost")) { | 356 | else if (equal_Command(cmd, "window.focus.lost")) { |
341 | #if !defined (iPlatformMobile) /* apps don't share input focus on mobile */ | 357 | #if !defined (iPlatformMobile) /* apps don't share input focus on mobile */ |
342 | setFocus_Widget(NULL); | 358 | setFocus_Widget(NULL); |
@@ -518,13 +534,17 @@ static iBool willPerformSearchQuery_(const iString *userInput) { | |||
518 | return !isEmpty_String(&prefs_App()->searchUrl) && !isLikelyUrl_String(userInput); | 534 | return !isEmpty_String(&prefs_App()->searchUrl) && !isLikelyUrl_String(userInput); |
519 | } | 535 | } |
520 | 536 | ||
537 | static void updateUrlInputContentPadding_(iWidget *navBar) { | ||
538 | iInputWidget *url = findChild_Widget(navBar, "url"); | ||
539 | const iWidget *indicators = findChild_Widget(navBar, "url.rightembed"); | ||
540 | setContentPadding_InputWidget(url, -1, | ||
541 | width_Widget(indicators)); | ||
542 | } | ||
543 | |||
521 | static void showSearchQueryIndicator_(iBool show) { | 544 | static void showSearchQueryIndicator_(iBool show) { |
522 | iWidget *indicator = findWidget_App("input.indicator.search"); | 545 | iWidget *indicator = findWidget_App("input.indicator.search"); |
523 | showCollapsed_Widget(indicator, show); | 546 | showCollapsed_Widget(indicator, show); |
524 | iAssert(isInstance_Object(parent_Widget(parent_Widget(indicator)), &Class_InputWidget)); | 547 | updateUrlInputContentPadding_(findWidget_Root("navbar")); |
525 | iInputWidget *url = (iInputWidget *) parent_Widget(parent_Widget(indicator)); | ||
526 | setContentPadding_InputWidget(url, -1, contentPadding_InputWidget(url).left + | ||
527 | (show ? width_Widget(indicator) : 0)); | ||
528 | } | 548 | } |
529 | 549 | ||
530 | static int navBarAvailableSpace_(iWidget *navBar) { | 550 | static int navBarAvailableSpace_(iWidget *navBar) { |
@@ -992,6 +1012,7 @@ void createUserInterface_Root(iRoot *d) { | |||
992 | setFlags_Widget(as_Widget(url), resizeHeightOfChildren_WidgetFlag, iTrue); | 1012 | setFlags_Widget(as_Widget(url), resizeHeightOfChildren_WidgetFlag, iTrue); |
993 | setSelectAllOnFocus_InputWidget(url, iTrue); | 1013 | setSelectAllOnFocus_InputWidget(url, iTrue); |
994 | setId_Widget(as_Widget(url), "url"); | 1014 | setId_Widget(as_Widget(url), "url"); |
1015 | setMaxLayoutLines_InputWidget(url, 1); | ||
995 | setUrlContent_InputWidget(url, iTrue); | 1016 | setUrlContent_InputWidget(url, iTrue); |
996 | setNotifyEdits_InputWidget(url, iTrue); | 1017 | setNotifyEdits_InputWidget(url, iTrue); |
997 | setTextCStr_InputWidget(url, "gemini://"); | 1018 | setTextCStr_InputWidget(url, "gemini://"); |
@@ -1017,7 +1038,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1017 | moveToParentRightEdge_WidgetFlag); | 1038 | moveToParentRightEdge_WidgetFlag); |
1018 | /* Feeds refresh indicator is inside the input field. */ { | 1039 | /* Feeds refresh indicator is inside the input field. */ { |
1019 | iLabelWidget *queryInd = | 1040 | iLabelWidget *queryInd = |
1020 | new_LabelWidget(uiTextAction_ColorEscape "\u21d2 ${status.query}", NULL); | 1041 | new_LabelWidget(uiTextAction_ColorEscape "${status.query} \u21a9", NULL); |
1021 | setId_Widget(as_Widget(queryInd), "input.indicator.search"); | 1042 | setId_Widget(as_Widget(queryInd), "input.indicator.search"); |
1022 | setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId); | 1043 | setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId); |
1023 | setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId); | 1044 | setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId); |
diff --git a/src/ui/root.h b/src/ui/root.h index 00555224..96864a15 100644 --- a/src/ui/root.h +++ b/src/ui/root.h | |||
@@ -11,6 +11,7 @@ struct Impl_Root { | |||
11 | iWidget * widget; | 11 | iWidget * widget; |
12 | iPtrArray *onTop; /* order is important; last one is topmost */ | 12 | iPtrArray *onTop; /* order is important; last one is topmost */ |
13 | iPtrSet * pendingDestruction; | 13 | iPtrSet * pendingDestruction; |
14 | iBool pendingArrange; | ||
14 | int loadAnimTimer; | 15 | int loadAnimTimer; |
15 | iColor tmPalette[tmMax_ColorId]; /* theme-specific palette */ | 16 | iColor tmPalette[tmMax_ColorId]; /* theme-specific palette */ |
16 | }; | 17 | }; |
@@ -28,6 +29,7 @@ iAnyObject *findWidget_Root (const char *id); /* under curre | |||
28 | 29 | ||
29 | iPtrArray * onTop_Root (iRoot *); | 30 | iPtrArray * onTop_Root (iRoot *); |
30 | void destroyPending_Root (iRoot *); | 31 | void destroyPending_Root (iRoot *); |
32 | void postArrange_Root (iRoot *); | ||
31 | 33 | ||
32 | void updateMetrics_Root (iRoot *); | 34 | void updateMetrics_Root (iRoot *); |
33 | void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */ | 35 | void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */ |
@@ -39,4 +41,4 @@ iRect rect_Root (const iRoot *); | |||
39 | iRect safeRect_Root (const iRoot *); | 41 | iRect safeRect_Root (const iRoot *); |
40 | iInt2 visibleSize_Root (const iRoot *); /* may be obstructed by software keyboard */ | 42 | iInt2 visibleSize_Root (const iRoot *); /* may be obstructed by software keyboard */ |
41 | iBool isNarrow_Root (const iRoot *); | 43 | iBool isNarrow_Root (const iRoot *); |
42 | int appIconSize_Root (void); \ No newline at end of file | 44 | int appIconSize_Root (void); |
diff --git a/src/ui/text.c b/src/ui/text.c index 8cf4464e..9838fb00 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -923,6 +923,13 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
923 | } | 923 | } |
924 | /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */ | 924 | /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */ |
925 | if (ch == '\n') { | 925 | if (ch == '\n') { |
926 | if (args->xposLimit > 0 && ~mode & noWrapFlag_RunMode) { | ||
927 | /* Stop the line here, this is a hard warp. */ | ||
928 | if (args->continueFrom_out) { | ||
929 | *args->continueFrom_out = chPos; | ||
930 | } | ||
931 | break; | ||
932 | } | ||
926 | xpos = xposExtend = orig.x; | 933 | xpos = xposExtend = orig.x; |
927 | ypos += d->height; | 934 | ypos += d->height; |
928 | prevCh = ch; | 935 | prevCh = ch; |
diff --git a/src/ui/util.c b/src/ui/util.c index 92cf85b7..04cdf27f 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -378,10 +378,20 @@ void init_Click(iClick *d, iAnyObject *widget, int button) { | |||
378 | d->isActive = iFalse; | 378 | d->isActive = iFalse; |
379 | d->button = button; | 379 | d->button = button; |
380 | d->bounds = as_Widget(widget); | 380 | d->bounds = as_Widget(widget); |
381 | d->minHeight = 0; | ||
381 | d->startPos = zero_I2(); | 382 | d->startPos = zero_I2(); |
382 | d->pos = zero_I2(); | 383 | d->pos = zero_I2(); |
383 | } | 384 | } |
384 | 385 | ||
386 | iBool contains_Click(const iClick *d, iInt2 coord) { | ||
387 | if (d->minHeight) { | ||
388 | iRect rect = bounds_Widget(d->bounds); | ||
389 | rect.size.y = iMax(d->minHeight, rect.size.y); | ||
390 | return contains_Rect(rect, coord); | ||
391 | } | ||
392 | return contains_Widget(d->bounds, coord); | ||
393 | } | ||
394 | |||
385 | enum iClickResult processEvent_Click(iClick *d, const SDL_Event *event) { | 395 | enum iClickResult processEvent_Click(iClick *d, const SDL_Event *event) { |
386 | if (event->type == SDL_MOUSEMOTION) { | 396 | if (event->type == SDL_MOUSEMOTION) { |
387 | const iInt2 pos = init_I2(event->motion.x, event->motion.y); | 397 | const iInt2 pos = init_I2(event->motion.x, event->motion.y); |
@@ -403,7 +413,7 @@ enum iClickResult processEvent_Click(iClick *d, const SDL_Event *event) { | |||
403 | } | 413 | } |
404 | if (!d->isActive) { | 414 | if (!d->isActive) { |
405 | if (mb->state == SDL_PRESSED) { | 415 | if (mb->state == SDL_PRESSED) { |
406 | if (contains_Widget(d->bounds, pos)) { | 416 | if (contains_Click(d, pos)) { |
407 | d->isActive = iTrue; | 417 | d->isActive = iTrue; |
408 | d->startPos = d->pos = pos; | 418 | d->startPos = d->pos = pos; |
409 | setMouseGrab_Widget(d->bounds); | 419 | setMouseGrab_Widget(d->bounds); |
@@ -413,7 +423,7 @@ enum iClickResult processEvent_Click(iClick *d, const SDL_Event *event) { | |||
413 | } | 423 | } |
414 | else { /* Active. */ | 424 | else { /* Active. */ |
415 | if (mb->state == SDL_RELEASED) { | 425 | if (mb->state == SDL_RELEASED) { |
416 | enum iClickResult result = contains_Widget(d->bounds, pos) | 426 | enum iClickResult result = contains_Click(d, pos) |
417 | ? finished_ClickResult | 427 | ? finished_ClickResult |
418 | : aborted_ClickResult; | 428 | : aborted_ClickResult; |
419 | d->isActive = iFalse; | 429 | d->isActive = iFalse; |
@@ -891,6 +901,14 @@ static iBool isTabPage_Widget_(const iWidget *tabs, const iWidget *page) { | |||
891 | return page && page->parent == findChild_Widget(tabs, "tabs.pages"); | 901 | return page && page->parent == findChild_Widget(tabs, "tabs.pages"); |
892 | } | 902 | } |
893 | 903 | ||
904 | static void unfocusFocusInsideTabPage_(const iWidget *page) { | ||
905 | iWidget *focus = focus_Widget(); | ||
906 | if (page && focus && hasParent_Widget(focus, page)) { | ||
907 | printf("unfocus inside page: %p\n", focus); | ||
908 | setFocus_Widget(NULL); | ||
909 | } | ||
910 | } | ||
911 | |||
894 | static iBool tabSwitcher_(iWidget *tabs, const char *cmd) { | 912 | static iBool tabSwitcher_(iWidget *tabs, const char *cmd) { |
895 | if (equal_Command(cmd, "tabs.switch")) { | 913 | if (equal_Command(cmd, "tabs.switch")) { |
896 | iWidget *target = pointerLabel_Command(cmd, "page"); | 914 | iWidget *target = pointerLabel_Command(cmd, "page"); |
@@ -898,6 +916,7 @@ static iBool tabSwitcher_(iWidget *tabs, const char *cmd) { | |||
898 | target = findChild_Widget(tabs, cstr_Rangecc(range_Command(cmd, "id"))); | 916 | target = findChild_Widget(tabs, cstr_Rangecc(range_Command(cmd, "id"))); |
899 | } | 917 | } |
900 | if (!target) return iFalse; | 918 | if (!target) return iFalse; |
919 | unfocusFocusInsideTabPage_(currentTabPage_Widget(tabs)); | ||
901 | if (flags_Widget(target) & focusable_WidgetFlag) { | 920 | if (flags_Widget(target) & focusable_WidgetFlag) { |
902 | setFocus_Widget(target); | 921 | setFocus_Widget(target); |
903 | } | 922 | } |
@@ -915,6 +934,7 @@ static iBool tabSwitcher_(iWidget *tabs, const char *cmd) { | |||
915 | } | 934 | } |
916 | } | 935 | } |
917 | else if (equal_Command(cmd, "tabs.next") || equal_Command(cmd, "tabs.prev")) { | 936 | else if (equal_Command(cmd, "tabs.next") || equal_Command(cmd, "tabs.prev")) { |
937 | unfocusFocusInsideTabPage_(currentTabPage_Widget(tabs)); | ||
918 | iWidget *pages = findChild_Widget(tabs, "tabs.pages"); | 938 | iWidget *pages = findChild_Widget(tabs, "tabs.pages"); |
919 | int tabIndex = 0; | 939 | int tabIndex = 0; |
920 | iConstForEach(ObjectList, i, pages->children) { | 940 | iConstForEach(ObjectList, i, pages->children) { |
@@ -1881,6 +1901,9 @@ iWidget *makeDialogButtons_Widget(const iMenuItem *actions, size_t numActions) { | |||
1881 | } | 1901 | } |
1882 | iLabelWidget *button = | 1902 | iLabelWidget *button = |
1883 | addChild_Widget(div, iClob(newKeyMods_LabelWidget(label, key, kmods, cmd))); | 1903 | addChild_Widget(div, iClob(newKeyMods_LabelWidget(label, key, kmods, cmd))); |
1904 | if (isDefault) { | ||
1905 | setId_Widget(as_Widget(button), "default"); | ||
1906 | } | ||
1884 | setFlags_Widget(as_Widget(button), alignLeft_WidgetFlag | drawKey_WidgetFlag, isDefault); | 1907 | setFlags_Widget(as_Widget(button), alignLeft_WidgetFlag | drawKey_WidgetFlag, isDefault); |
1885 | setFont_LabelWidget(button, isDefault ? fonts[1] : fonts[0]); | 1908 | setFont_LabelWidget(button, isDefault ? fonts[1] : fonts[0]); |
1886 | } | 1909 | } |
@@ -2175,6 +2198,10 @@ static void addDialogInputWithHeading_(iWidget *headings, iWidget *values, const | |||
2175 | setPadding_Widget(as_Widget(head), 0, gap_UI, 0, 0); | 2198 | setPadding_Widget(as_Widget(head), 0, gap_UI, 0, 0); |
2176 | #endif | 2199 | #endif |
2177 | setId_Widget(addChild_Widget(values, input), inputId); | 2200 | setId_Widget(addChild_Widget(values, input), inputId); |
2201 | if (deviceType_App() != phone_AppDeviceType) { | ||
2202 | /* Ensure that the label has the same height as the input widget. */ | ||
2203 | as_Widget(head)->sizeRef = as_Widget(input); | ||
2204 | } | ||
2178 | } | 2205 | } |
2179 | 2206 | ||
2180 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, | 2207 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, |
@@ -2202,10 +2229,13 @@ iWidget *makePreferences_Widget(void) { | |||
2202 | /* General preferences. */ { | 2229 | /* General preferences. */ { |
2203 | appendTwoColumnPage_(tabs, "${heading.prefs.general}", '1', &headings, &values); | 2230 | appendTwoColumnPage_(tabs, "${heading.prefs.general}", '1', &headings, &values); |
2204 | #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) | 2231 | #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) |
2205 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.downloads}"))); | 2232 | //addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.downloads}"))); |
2206 | setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.downloads"); | 2233 | //setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.downloads"); |
2234 | addPrefsInputWithHeading_(headings, values, "prefs.downloads", iClob(new_InputWidget(0))); | ||
2207 | #endif | 2235 | #endif |
2208 | addPrefsInputWithHeading_(headings, values, "prefs.searchurl", iClob(new_InputWidget(0))); | 2236 | iInputWidget *searchUrl; |
2237 | addPrefsInputWithHeading_(headings, values, "prefs.searchurl", iClob(searchUrl = new_InputWidget(0))); | ||
2238 | setUrlContent_InputWidget(searchUrl, iTrue); | ||
2209 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); | 2239 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); |
2210 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); | 2240 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); |
2211 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.collapsepreonload}"))); | 2241 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.collapsepreonload}"))); |
diff --git a/src/ui/util.h b/src/ui/util.h index cbedefaa..50845280 100644 --- a/src/ui/util.h +++ b/src/ui/util.h | |||
@@ -154,6 +154,7 @@ struct Impl_Click { | |||
154 | int button; | 154 | int button; |
155 | int count; | 155 | int count; |
156 | iWidget *bounds; | 156 | iWidget *bounds; |
157 | int minHeight; | ||
157 | iInt2 startPos; | 158 | iInt2 startPos; |
158 | iInt2 pos; | 159 | iInt2 pos; |
159 | }; | 160 | }; |
@@ -166,6 +167,7 @@ iBool isMoved_Click (const iClick *); | |||
166 | iInt2 pos_Click (const iClick *); | 167 | iInt2 pos_Click (const iClick *); |
167 | iRect rect_Click (const iClick *); | 168 | iRect rect_Click (const iClick *); |
168 | iInt2 delta_Click (const iClick *); | 169 | iInt2 delta_Click (const iClick *); |
170 | iBool contains_Click (const iClick *, iInt2 coord); | ||
169 | 171 | ||
170 | /*-----------------------------------------------------------------------------------------------*/ | 172 | /*-----------------------------------------------------------------------------------------------*/ |
171 | 173 | ||
diff --git a/src/ui/widget.c b/src/ui/widget.c index 67ce1345..c1c920d2 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c | |||
@@ -54,6 +54,7 @@ void init_Widget(iWidget *d) { | |||
54 | d->flags = 0; | 54 | d->flags = 0; |
55 | d->rect = zero_Rect(); | 55 | d->rect = zero_Rect(); |
56 | d->minSize = zero_I2(); | 56 | d->minSize = zero_I2(); |
57 | d->sizeRef = NULL; | ||
57 | d->bgColor = none_ColorId; | 58 | d->bgColor = none_ColorId; |
58 | d->frameColor = none_ColorId; | 59 | d->frameColor = none_ColorId; |
59 | init_Anim(&d->visualOffset, 0.0f); | 60 | init_Anim(&d->visualOffset, 0.0f); |
@@ -329,6 +330,9 @@ static void setWidth_Widget_(iWidget *d, int width) { | |||
329 | 330 | ||
330 | static void setHeight_Widget_(iWidget *d, int height) { | 331 | static void setHeight_Widget_(iWidget *d, int height) { |
331 | iAssert(height >= 0); | 332 | iAssert(height >= 0); |
333 | if (d->sizeRef) { | ||
334 | return; /* height defined by another widget */ | ||
335 | } | ||
332 | TRACE(d, "attempt to set height to %d (current: %d, min height: %d)", height, d->rect.size.y, d->minSize.y); | 336 | TRACE(d, "attempt to set height to %d (current: %d, min height: %d)", height, d->rect.size.y, d->minSize.y); |
333 | height = iMax(height, d->minSize.y); | 337 | height = iMax(height, d->minSize.y); |
334 | if (~d->flags & fixedHeight_WidgetFlag) { //} || d->flags & collapse_WidgetFlag) { | 338 | if (~d->flags & fixedHeight_WidgetFlag) { //} || d->flags & collapse_WidgetFlag) { |
@@ -415,6 +419,10 @@ static void boundsOfChildren_Widget_(const iWidget *d, iRect *bounds_out) { | |||
415 | 419 | ||
416 | static void arrange_Widget_(iWidget *d) { | 420 | static void arrange_Widget_(iWidget *d) { |
417 | TRACE(d, "arranging..."); | 421 | TRACE(d, "arranging..."); |
422 | if (d->sizeRef) { | ||
423 | d->rect.size.y = height_Widget(d->sizeRef); | ||
424 | TRACE(d, "use referenced height: %d", d->rect.size.y); | ||
425 | } | ||
418 | if (d->flags & moveToParentLeftEdge_WidgetFlag) { | 426 | if (d->flags & moveToParentLeftEdge_WidgetFlag) { |
419 | d->rect.pos.x = d->padding[0]; /* FIXME: Shouldn't this be d->parent->padding[0]? */ | 427 | d->rect.pos.x = d->padding[0]; /* FIXME: Shouldn't this be d->parent->padding[0]? */ |
420 | TRACE(d, "move to parent left edge: %d", d->rect.pos.x); | 428 | TRACE(d, "move to parent left edge: %d", d->rect.pos.x); |
@@ -1035,6 +1043,7 @@ void drawBackground_Widget(const iWidget *d) { | |||
1035 | init_Paint(&p); | 1043 | init_Paint(&p); |
1036 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); | 1044 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); |
1037 | } | 1045 | } |
1046 | |||
1038 | if (fadeBackground && ~d->flags & noFadeBackground_WidgetFlag) { | 1047 | if (fadeBackground && ~d->flags & noFadeBackground_WidgetFlag) { |
1039 | iPaint p; | 1048 | iPaint p; |
1040 | init_Paint(&p); | 1049 | init_Paint(&p); |
@@ -1131,10 +1140,10 @@ void drawChildren_Widget(const iWidget *d) { | |||
1131 | } | 1140 | } |
1132 | } | 1141 | } |
1133 | /* Root draws the on-top widgets on top of everything else. */ | 1142 | /* Root draws the on-top widgets on top of everything else. */ |
1134 | if (!d->parent) { | 1143 | if (d == d->root->widget) { |
1135 | iConstForEach(PtrArray, i, onTop_Root(d->root)) { | 1144 | iConstForEach(PtrArray, i, onTop_Root(d->root)) { |
1136 | const iWidget *top = *i.value; | 1145 | const iWidget *top = *i.value; |
1137 | draw_Widget(top); | 1146 | class_Widget(top)->draw(top); |
1138 | } | 1147 | } |
1139 | } | 1148 | } |
1140 | } | 1149 | } |
diff --git a/src/ui/widget.h b/src/ui/widget.h index 5b6b18e1..5c05e917 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h | |||
@@ -134,6 +134,7 @@ struct Impl_Widget { | |||
134 | int64_t flags; | 134 | int64_t flags; |
135 | iRect rect; | 135 | iRect rect; |
136 | iInt2 minSize; | 136 | iInt2 minSize; |
137 | iWidget * sizeRef; | ||
137 | int padding[4]; /* left, top, right, bottom */ | 138 | int padding[4]; /* left, top, right, bottom */ |
138 | iAnim visualOffset; | 139 | iAnim visualOffset; |
139 | int bgColor; | 140 | int bgColor; |