1 /** @file hu_lib.cpp  HUD widget library.
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 "hu_lib.h"
23 
24 #include <QList>
25 
26 /// @todo remove me:
27 #include "hud/widgets/automapwidget.h"
28 #include "hud/widgets/chatwidget.h"
29 #include "hud/widgets/groupwidget.h"
30 #include "hud/widgets/playerlogwidget.h"
31 #include "menu/widgets/lineeditwidget.h"
32 #include "menu/widgets/sliderwidget.h"
33 
34 using namespace de;
35 using namespace common;
36 
37 static bool inited;
38 
39 static QList<HudWidget *> widgets;
40 
41 static ui_rendstate_t uiRS;
42 ui_rendstate_t const *uiRendState = &uiRS;
43 
nextUnusedId()44 static uiwidgetid_t nextUnusedId()
45 {
46     return uiwidgetid_t(widgets.count());
47 }
48 
clearWidgets()49 static void clearWidgets()
50 {
51     qDeleteAll(widgets); widgets.clear();
52 }
53 
GUI_Init()54 void GUI_Init()
55 {
56     if(inited) return;
57 
58     clearWidgets();
59     ChatWidget::loadMacros();
60 
61     inited = true;
62 
63     GUI_LoadResources();
64 }
65 
GUI_Shutdown()66 void GUI_Shutdown()
67 {
68     if(!inited) return;
69     clearWidgets();
70     inited = false;
71 }
72 
GUI_AddWidget(HudWidget * wi)73 HudWidget *GUI_AddWidget(HudWidget *wi)
74 {
75     DENG2_ASSERT(inited);
76     if(wi)
77     {
78         wi->setId(nextUnusedId());
79         widgets << wi;
80     }
81     return wi;
82 }
83 
GUI_TryFindWidgetById(uiwidgetid_t id)84 HudWidget *GUI_TryFindWidgetById(uiwidgetid_t id)
85 {
86     if(!inited) return 0;  // GUI not available.
87     if(id >= 0)
88     {
89         for(HudWidget *wi : widgets)
90         {
91             if(wi->id() == id) return wi;
92         }
93     }
94     return nullptr;  // Not found.
95 }
96 
GUI_FindWidgetById(uiwidgetid_t id)97 HudWidget &GUI_FindWidgetById(uiwidgetid_t id)
98 {
99     if(HudWidget *wi = GUI_TryFindWidgetById(id)) return *wi;
100     throw Error("GUI_FindWidgetById", "Unknown widget id #" + String::number(id));
101 }
102 
GUI_UpdateWidgetGeometry(HudWidget * wi)103 void GUI_UpdateWidgetGeometry(HudWidget *wi)
104 {
105     if(!wi) return;
106 
107     Rect_SetXY(&wi->geometry(), 0, 0);
108     wi->updateGeometry(wi);
109 
110     if(Rect_Width (&wi->geometry()) <= 0 ||
111        Rect_Height(&wi->geometry()) <= 0) return;
112 
113     if(wi->alignment() & ALIGN_RIGHT)
114         Rect_SetX(&wi->geometry(), Rect_X(&wi->geometry()) - Rect_Width(&wi->geometry()));
115     else if(!(wi->alignment() & ALIGN_LEFT))
116         Rect_SetX(&wi->geometry(), Rect_X(&wi->geometry()) - Rect_Width(&wi->geometry()) / 2);
117 
118     if(wi->alignment() & ALIGN_BOTTOM)
119         Rect_SetY(&wi->geometry(), Rect_Y(&wi->geometry()) - Rect_Height(&wi->geometry()));
120     else if(!(wi->alignment() & ALIGN_TOP))
121         Rect_SetY(&wi->geometry(), Rect_Y(&wi->geometry()) - Rect_Height(&wi->geometry()) / 2);
122 }
123 
124 #if defined(UI_DEBUG)
drawWidgetGeometry(HudWidget * wi)125 static void drawWidgetGeometry(HudWidget *wi)
126 {
127     DENG2_ASSERT(wi);
128 
129     RectRaw geometry;
130     Rect_Raw(&wi->geometry(), &geometry);
131 
132     DGL_Color3f(1, 1, 1);
133     DGL_Begin(DGL_LINES);
134         DGL_Vertex2f(geometry.origin.x, geometry.origin.y);
135         DGL_Vertex2f(geometry.origin.x + geometry.size.width, geometry.origin.y);
136         DGL_Vertex2f(geometry.origin.x + geometry.size.width, geometry.origin.y);
137         DGL_Vertex2f(geometry.origin.x + geometry.size.width, geometry.origin.y + geometry.size.height);
138         DGL_Vertex2f(geometry.origin.x + geometry.size.width, geometry.origin.y + geometry.size.height);
139         DGL_Vertex2f(geometry.origin.x, geometry.origin.y + geometry.size.height);
140         DGL_Vertex2f(geometry.origin.x, geometry.origin.y + geometry.size.height);
141         DGL_Vertex2f(geometry.origin.x, geometry.origin.y);
142     DGL_End();
143 }
144 
drawWidgetAvailableSpace(HudWidget * wi)145 static void drawWidgetAvailableSpace(HudWidget *wi)
146 {
147     DENG2_ASSERT(wi);
148     DGL_Color4f(0, .4f, 0, .1f);
149     DGL_DrawRectf2(Rect_X(&wi->geometry()), Rect_Y(&wi->geometry()), wi->maximumSize().width, wi->maximumSize().height);
150 }
151 #endif
152 
drawWidget2(HudWidget * wi,Point2Raw const * offset=nullptr)153 static void drawWidget2(HudWidget *wi, Point2Raw const *offset = nullptr)
154 {
155     DENG2_ASSERT(wi);
156 
157 #if defined(UI_DEBUG)
158     drawWidgetAvailableSpace(wi);
159 #endif
160 
161     if(wi->drawer && wi->opacity() > .0001f)
162     {
163         Point2Raw origin;
164         Point2_Raw(Rect_Origin(&wi->geometry()), &origin);
165 
166         // Configure the page render state.
167         uiRS.pageAlpha = wi->opacity();
168 
169         FR_PushAttrib();
170 
171         DGL_MatrixMode(DGL_MODELVIEW);
172         DGL_Translatef(origin.x, origin.y, 0);
173 
174         // Do not pass a zero length offset.
175         wi->drawer(wi, ((offset && (offset->x || offset->y))? offset : nullptr));
176 
177         FR_PopAttrib();
178 
179         DGL_MatrixMode(DGL_MODELVIEW);
180         DGL_Translatef(-origin.x, -origin.y, 0);
181     }
182 
183 #if defined(UI_DEBUG)
184     drawWidgetGeometry(wi);
185 #endif
186 }
187 
drawWidget(HudWidget * wi,Point2Raw const * origin=nullptr)188 static void drawWidget(HudWidget *wi, Point2Raw const *origin = nullptr)
189 {
190     DENG2_ASSERT(wi);
191 
192     if(origin)
193     {
194         DGL_MatrixMode(DGL_MODELVIEW);
195         DGL_Translatef(origin->x, origin->y, 0);
196     }
197 
198     // First ourself.
199     drawWidget2(wi);
200 
201     if(auto *group = maybeAs<GroupWidget>(wi))
202     {
203         // Then our children.
204         group->forAllChildren([] (HudWidget &child)
205         {
206             drawWidget(&child);
207             return LoopContinue;
208         });
209     }
210 
211     if(origin)
212     {
213         DGL_MatrixMode(DGL_MODELVIEW);
214         DGL_Translatef(-origin->x, -origin->y, 0);
215     }
216 }
217 
GUI_DrawWidget(HudWidget * wi,Point2Raw const * offset)218 void GUI_DrawWidget(HudWidget *wi, Point2Raw const *offset)
219 {
220     if(!wi) return;
221     if(wi->maximumWidth() < 1 || wi->maximumHeight() < 1) return;
222     if(wi->opacity() <= 0) return;
223 
224     FR_PushAttrib();
225     FR_LoadDefaultAttrib();
226     FR_SetLeading(0);
227 
228     GUI_UpdateWidgetGeometry(wi);
229 
230     FR_PopAttrib();
231 
232     // Draw.
233     FR_PushAttrib();
234     FR_LoadDefaultAttrib();
235     FR_SetLeading(0);
236 
237     // Do not pass a zero length offset.
238     drawWidget(wi, ((offset && (offset->x || offset->y))? offset : nullptr));
239 
240     FR_PopAttrib();
241 }
242 
GUI_DrawWidgetXY(HudWidget * wi,int x,int y)243 void GUI_DrawWidgetXY(HudWidget *wi, int x, int y)
244 {
245     Point2Raw origin = {{{x, y}}};
246     GUI_DrawWidget(wi, &origin);
247 }
248 
GUI_SpriteSize(int sprite,float scale,int * width,int * height)249 void GUI_SpriteSize(int sprite, float scale, int *width, int *height)
250 {
251     spriteinfo_t info;
252     if(!width && !height) return;
253     if(!R_GetSpriteInfo(sprite, 0, &info)) return;
254 
255     if(width)  *width  = info.geometry.size.width  * scale;
256     if(height) *height = info.geometry.size.height * scale;
257 }
258 
GUI_DrawSprite(int sprite,float x,float y,hotloc_t hotspot,float scale,float alpha,dd_bool flip,int * drawnWidth,int * drawnHeight)259 void GUI_DrawSprite(int sprite, float x, float y, hotloc_t hotspot,
260     float scale, float alpha, dd_bool flip, int* drawnWidth, int *drawnHeight)
261 {
262     spriteinfo_t info;
263 
264     if(!(alpha > 0)) return;
265 
266     alpha = MINMAX_OF(0.f, alpha, 1.f);
267     R_GetSpriteInfo(sprite, 0, &info);
268 
269     switch(hotspot)
270     {
271     case HOT_BRIGHT:
272         y -= info.geometry.size.height * scale;
273         // Fall through.
274     case HOT_TRIGHT:
275         x -= info.geometry.size.width * scale;
276         break;
277 
278     case HOT_BLEFT:
279         y -= info.geometry.size.height * scale;
280         break;
281     default: break;
282     }
283 
284     DGL_SetPSprite(info.material);
285     DGL_Enable(DGL_TEXTURE_2D);
286 
287     DGL_Color4f(1, 1, 1, alpha);
288     DGL_Begin(DGL_QUADS);
289         DGL_TexCoord2f(0, flip * info.texCoord[0], 0);
290         DGL_Vertex2f(x, y);
291 
292         DGL_TexCoord2f(0, !flip * info.texCoord[0], 0);
293         DGL_Vertex2f(x + info.geometry.size.width * scale, y);
294 
295         DGL_TexCoord2f(0, !flip * info.texCoord[0], info.texCoord[1]);
296         DGL_Vertex2f(x + info.geometry.size.width * scale, y + info.geometry.size.height * scale);
297 
298         DGL_TexCoord2f(0, flip * info.texCoord[0], info.texCoord[1]);
299         DGL_Vertex2f(x, y + info.geometry.size.height * scale);
300     DGL_End();
301 
302     DGL_Disable(DGL_TEXTURE_2D);
303 
304     if(drawnWidth)  *drawnWidth  = info.geometry.size.width  * scale;
305     if(drawnHeight) *drawnHeight = info.geometry.size.height * scale;
306 }
307 
GUI_Register()308 void GUI_Register()
309 {
310     AutomapWidget::consoleRegister();
311     ChatWidget::consoleRegister();
312     PlayerLogWidget::consoleRegister();
313 }
314 
GUI_LoadResources()315 void GUI_LoadResources()
316 {
317     if(Get(DD_NOVIDEO)) return;
318 
319     AutomapWidget::prepareAssets();
320     menu::LineEditWidget::loadResources();
321     menu::SliderWidget::loadResources();
322 }
323 
GUI_ReleaseResources()324 void GUI_ReleaseResources()
325 {
326     if(Get(DD_NOVIDEO)) return;
327 
328     AutomapWidget::prepareAssets();
329 
330     for(HudWidget *wi : widgets)
331     {
332         if(auto *automap = maybeAs<AutomapWidget>(wi))
333         {
334             automap->reset();
335         }
336     }
337 }
338