1 /** @file lineeditwidget.cpp UI widget for an editable line of text.
2 *
3 * @authors Copyright © 2005-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2005-2014 Daniel Swanson <danij@dengine.net>
5 *
6 * @par License
7 * GPL: http://www.gnu.org/licenses/gpl.html
8 *
9 * <small>This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2 of the License, or (at your
12 * option) any later version. This program is distributed in the hope that it
13 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15 * Public License for more details. You should have received a copy of the GNU
16 * General Public License along with this program; if not, write to the Free
17 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18 * 02110-1301 USA</small>
19 */
20
21 #include "common.h"
22 #include "menu/widgets/lineeditwidget.h"
23
24 #include "hu_menu.h" // menu sounds
25 #include "hu_stuff.h" // shiftXForm
26 #include "menu/page.h" // mnRendState
27
28 using namespace de;
29
30 namespace common {
31 namespace menu {
32
33 static patchid_t pEditLeft;
34 static patchid_t pEditRight;
35 static patchid_t pEditMiddle;
36
DENG2_PIMPL_NOREF(LineEditWidget)37 DENG2_PIMPL_NOREF(LineEditWidget)
38 {
39 String text;
40 String oldText; ///< For restoring a canceled edit.
41 String emptyText; ///< Used when value is empty.
42 int maxLength = 0;
43 int maxVisibleChars = 0;
44 };
45
LineEditWidget()46 LineEditWidget::LineEditWidget()
47 : Widget()
48 , d(new Impl)
49 {
50 setFont(MENU_FONT1);
51 setColor(MENU_COLOR1);
52 }
53
~LineEditWidget()54 LineEditWidget::~LineEditWidget()
55 {}
56
loadResources()57 void LineEditWidget::loadResources() // static
58 {
59 #if defined(MNDATA_EDIT_BACKGROUND_PATCH_LEFT)
60 pEditLeft = R_DeclarePatch(MNDATA_EDIT_BACKGROUND_PATCH_LEFT);
61 #else
62 pEditLeft = 0;
63 #endif
64 #if defined(MNDATA_EDIT_BACKGROUND_PATCH_RIGHT)
65 pEditRight = R_DeclarePatch(MNDATA_EDIT_BACKGROUND_PATCH_RIGHT);
66 #else
67 pEditRight = 0;
68 #endif
69 pEditMiddle = R_DeclarePatch(MNDATA_EDIT_BACKGROUND_PATCH_MIDDLE);
70 }
71
drawEditBackground(Vector2i const & origin,int width,float alpha)72 static void drawEditBackground(Vector2i const &origin, int width, float alpha)
73 {
74 DGL_Color4f(1, 1, 1, alpha);
75
76 int leftOffset = 0;
77 patchinfo_t leftInfo;
78 if(R_GetPatchInfo(pEditLeft, &leftInfo))
79 {
80 DGL_SetPatch(pEditLeft, DGL_CLAMP_TO_EDGE, DGL_CLAMP_TO_EDGE);
81 DGL_DrawRectf2(origin.x, origin.y, leftInfo.geometry.size.width, leftInfo.geometry.size.height);
82 leftOffset = leftInfo.geometry.size.width;
83 }
84
85 int rightOffset = 0;
86 patchinfo_t rightInfo;
87 if(R_GetPatchInfo(pEditRight, &rightInfo))
88 {
89 DGL_SetPatch(pEditRight, DGL_CLAMP_TO_EDGE, DGL_CLAMP_TO_EDGE);
90 DGL_DrawRectf2(origin.x + width - rightInfo.geometry.size.width, origin.y, rightInfo.geometry.size.width, rightInfo.geometry.size.height);
91 rightOffset = rightInfo.geometry.size.width;
92 }
93
94 patchinfo_t middleInfo;
95 if(R_GetPatchInfo(pEditMiddle, &middleInfo))
96 {
97 if (!pEditLeft && !pEditRight)
98 {
99 // Stretch the middle patch to the desired width.
100 DGL_SetPatch(pEditMiddle, DGL_CLAMP_TO_EDGE, DGL_CLAMP_TO_EDGE);
101 DGL_DrawRectf2(origin.x, origin.y,
102 width, middleInfo.geometry.size.height);
103 }
104 else
105 {
106 DGL_SetPatch(pEditMiddle, DGL_REPEAT, DGL_REPEAT);
107 DGL_DrawRectf2Tiled(origin.x + leftOffset, origin.y, width - leftOffset - rightOffset, middleInfo.geometry.size.height, middleInfo.geometry.size.width, middleInfo.geometry.size.height);
108 }
109 }
110 }
111
draw() const112 void LineEditWidget::draw() const
113 {
114 fontid_t fontId = mnRendState->textFonts[font()];
115
116 Vector2i origin = geometry().topLeft + Vector2i(MNDATA_EDIT_OFFSET_X, MNDATA_EDIT_OFFSET_Y);
117
118 String useText;
119 float light = 1, textOpacity = mnRendState->pageAlpha;
120 if(!d->text.isEmpty())
121 {
122 useText = d->text;
123 }
124 else if(!(isActive() && isFocused()))
125 {
126 useText = d->emptyText;
127 light *= .5f;
128 textOpacity = mnRendState->pageAlpha * .75f;
129 }
130
131 DGL_Enable(DGL_TEXTURE_2D);
132 FR_SetFont(fontId);
133
134 const float fadeout = scrollingFadeout();
135
136 //int const numVisCharacters = de::clamp(0, useText.isNull()? 0 : useText.length(), d->maxVisibleChars);
137
138 drawEditBackground(origin + Vector2i(MNDATA_EDIT_BACKGROUND_OFFSET_X, MNDATA_EDIT_BACKGROUND_OFFSET_Y),
139 geometry().width(), mnRendState->pageAlpha * fadeout);
140
141 //if(string)
142 {
143 // float t = 0;
144 Vector4f color = Vector4f(Vector3f(cfg.common.menuTextColors[MNDATA_EDIT_TEXT_COLORIDX]), 1.f);
145
146 // Flash if focused?
147 if (!isActive()) /* && isFocused() && cfg.common.menuTextFlashSpeed > 0)
148 {
149 float const speed = cfg.common.menuTextFlashSpeed / 2.f;
150 t = (1 + sin(page().timer() / (float)TICSPERSEC * speed * DD_PI)) / 2;
151 }*/
152 {
153 color = selectionFlashColor(color);
154 }
155
156 // Vector4f color = de::lerp(Vector3f(cfg.common.menuTextColors[MNDATA_EDIT_TEXT_COLORIDX]), Vector3f(cfg.common.menuTextFlashColor), t);
157 color *= light;
158 color.w = textOpacity;
159
160 // Draw the text:
161 FR_SetColorAndAlpha(color.x, color.y, color.z, color.w * fadeout);
162 FR_DrawTextXY3(useText.toUtf8().constData(), origin.x, origin.y, ALIGN_TOPLEFT, Hu_MenuMergeEffectWithDrawTextFlags(0));
163
164 // Are we drawing a cursor?
165 if(isActive() && isFocused() && (menuTime & 8) &&
166 (!d->maxLength || d->text.length() < d->maxLength))
167 {
168 origin.x += FR_TextWidth(useText.toUtf8().constData());
169 FR_DrawCharXY3('_', origin.x, origin.y, ALIGN_TOPLEFT, Hu_MenuMergeEffectWithDrawTextFlags(0));
170 }
171 }
172
173 DGL_Disable(DGL_TEXTURE_2D);
174 }
175
maxLength() const176 int LineEditWidget::maxLength() const
177 {
178 return d->maxLength;
179 }
180
setMaxLength(int newMaxLength)181 LineEditWidget &LineEditWidget::setMaxLength(int newMaxLength)
182 {
183 newMaxLength = de::max(newMaxLength, 0);
184 if(d->maxLength != newMaxLength)
185 {
186 if(newMaxLength < d->maxLength)
187 {
188 d->text.truncate(newMaxLength);
189 d->oldText.truncate(newMaxLength);
190 }
191 d->maxLength = newMaxLength;
192 }
193 return *this;
194 }
195
text() const196 String LineEditWidget::text() const
197 {
198 return d->text;
199 }
200
setText(String const & newText,int flags)201 LineEditWidget &LineEditWidget::setText(String const &newText, int flags)
202 {
203 d->text = newText;
204 if(d->maxLength) d->text.truncate(d->maxLength);
205
206 if(flags & MNEDIT_STF_REPLACEOLD)
207 {
208 d->oldText = d->text;
209 }
210
211 if(!(flags & MNEDIT_STF_NO_ACTION))
212 {
213 execAction(Modified);
214 }
215 return *this;
216 }
217
setEmptyText(String const & newEmptyText)218 LineEditWidget &LineEditWidget::setEmptyText(String const &newEmptyText)
219 {
220 d->emptyText = newEmptyText;
221 return *this;
222 }
223
emptyText() const224 String LineEditWidget::emptyText() const
225 {
226 return d->emptyText;
227 }
228
229 /**
230 * Responds to alphanumeric input for edit fields.
231 */
handleEvent(event_t const & ev)232 int LineEditWidget::handleEvent(event_t const &ev)
233 {
234 if(!isActive() || ev.type != EV_KEY)
235 return false;
236
237 if(ev.data1 == DDKEY_RSHIFT)
238 {
239 shiftdown = (ev.state == EVS_DOWN || ev.state == EVS_REPEAT);
240 return true;
241 }
242
243 if(!(ev.state == EVS_DOWN || ev.state == EVS_REPEAT))
244 return false;
245
246 if(ev.data1 == DDKEY_BACKSPACE)
247 {
248 if(!d->text.isEmpty())
249 {
250 d->text.truncate(d->text.length() - 1);
251 execAction(Modified);
252 }
253 return true;
254 }
255
256 if(ev.data1 >= ' ' && ev.data1 <= 'z')
257 {
258 char ch = char(ev.data1);
259 if(shiftdown)
260 {
261 ch = shiftXForm[int(ch)];
262 }
263
264 // Filter out nasty charactemnRendState->
265 if(ch == '%')
266 return true;
267
268 if(!d->maxLength || d->text.length() < d->maxLength)
269 {
270 d->text += ch;
271 execAction(Modified);
272 }
273 return true;
274 }
275
276 return false;
277 }
278
handleCommand(menucommand_e cmd)279 int LineEditWidget::handleCommand(menucommand_e cmd)
280 {
281 if(cmd == MCMD_SELECT)
282 {
283 if(!isActive())
284 {
285 S_LocalSound(SFX_MENU_CYCLE, NULL);
286 setFlags(Active);
287 // Store a copy of the present text value so we can restore it.
288 d->oldText = d->text;
289 execAction(Activated);
290 }
291 else
292 {
293 S_LocalSound(SFX_MENU_ACCEPT, NULL);
294 d->oldText = d->text;
295 setFlags(Active, UnsetFlags);
296 execAction(Deactivated);
297 }
298 return true;
299 }
300
301 if(isActive())
302 {
303 switch(cmd)
304 {
305 case MCMD_NAV_OUT:
306 d->text = d->oldText;
307 setFlags(Active, UnsetFlags);
308 execAction(Closed);
309 return true;
310
311 // Eat all other navigation commands, when active.
312 case MCMD_NAV_LEFT:
313 case MCMD_NAV_RIGHT:
314 case MCMD_NAV_DOWN:
315 case MCMD_NAV_UP:
316 case MCMD_NAV_PAGEDOWN:
317 case MCMD_NAV_PAGEUP:
318 return true;
319
320 default: break;
321 }
322 }
323
324 return false; // Not eaten.
325 }
326
updateGeometry()327 void LineEditWidget::updateGeometry()
328 {
329 FR_SetFont(mnRendState->textFonts[font()]);
330 geometry().setSize(Vector2ui(FR_CharWidth('w') * d->maxLength - MNDATA_EDIT_BACKGROUND_OFFSET_X*2, 14));
331 }
332
333 } // namespace menu
334 } // namespace common
335