1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2001, Sven2
6 * Copyright (c) 2017-2020, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// generic user interface
19// all generic classes that do not fit into other C4Gui*-files
20
21#include <C4GuiDialogs.h>
22#include "C4GuiResource.h"
23#include <C4Include.h>
24#include <C4Gui.h>
25
26#include <C4FullScreen.h>
27#include <C4LoaderScreen.h>
28#include <C4Application.h>
29#include <C4Viewport.h>
30#include <C4Wrappers.h>
31#include <C4Log.h>
32#include <C4GamePadCon.h>
33
34namespace C4GUI
35{
36
37// Generic helpers
38
39bool ExpandHotkeyMarkup(StdStrBuf &sText, char &rcHotkey)
40{
41 const char *HotkeyMarkup = "<c ffffff7f>x</c>";
42 size_t iHotkeyMarkupLength = 17;
43 size_t iHotkeyMarkupHotkeyPos = 12;
44
45 int iHotkeyPos;
46 const char *szCheckText = sText.getData();
47 if (!szCheckText || (iHotkeyPos = SCharPos(cTarget: '&', szInStr: szCheckText)) < 0)
48 {
49 // hotkey not available
50 rcHotkey = 0;
51 return false;
52 }
53 // set hotkey
54 sText.Grow(iGrow: iHotkeyMarkupLength - 2);
55 char *szText = sText.GrabPointer();
56 char *szTextBegin = szText;
57 rcHotkey = szText[iHotkeyPos + 1]; char cOrigHotkey = rcHotkey;
58 if (Inside(ival: rcHotkey, lbound: 'a', rbound: 'z')) rcHotkey += 'A' - 'a';
59 // mark hotkey
60 size_t iTextLen = SLen(sptr: szText);
61 szText += iHotkeyPos; iTextLen -= iHotkeyPos;
62 memmove(dest: szText + iHotkeyMarkupLength * sizeof(char), src: szText + 2 * sizeof(char), n: (iTextLen - 1) * sizeof(char));
63 memcpy(dest: szText, src: HotkeyMarkup, n: iHotkeyMarkupLength);
64 szText[iHotkeyMarkupHotkeyPos] = cOrigHotkey; // set original here, so no conversion to UpperCase
65 // write back string
66 sText.Take(pnData: szTextBegin);
67 // done, success
68 return true;
69}
70
71uint32_t MakeColorReadableOnBlack(uint32_t &rdwClr)
72{
73 // max alpha
74 uint32_t dwAlpha = std::max<uint32_t>(a: rdwClr >> 24 & 255, b: 0xff) << 24;
75 rdwClr &= 0xffffff;
76 // determine brightness
77 // 50% red, 87% green, 27% blue (max 164 * 255)
78 uint32_t r = (rdwClr >> 16 & 255), g = (rdwClr >> 8 & 255), b = (rdwClr & 255);
79 int32_t iLightness = r * 50 + g * 87 + b * 27;
80 // above 65/164 (*255) is OK
81 if (iLightness < 16575)
82 {
83 int32_t iInc = (16575 - iLightness) / 164;
84 // otherwise, lighten
85 rdwClr = (std::min<uint32_t>(a: r + iInc, b: 255) << 16) | (std::min<uint32_t>(a: g + iInc, b: 255) << 8) | std::min<uint32_t>(a: b + iInc, b: 255);
86 }
87 // return color and alpha
88 rdwClr |= dwAlpha;
89 return rdwClr;
90}
91
92void DynBarFacet::SetHorizontal(C4Surface &rBySfc, int iHeight, int iBorderWidth)
93{
94 if (!iHeight) iHeight = rBySfc.Hgt;
95 if (!iBorderWidth) iBorderWidth = iHeight;
96 fctBegin.Set(nsfc: &rBySfc, nx: 0, ny: 0, nwdt: iBorderWidth, nhgt: iHeight);
97 fctMiddle.Set(nsfc: &rBySfc, nx: iBorderWidth, ny: 0, nwdt: rBySfc.Wdt - 2 * iBorderWidth, nhgt: iHeight);
98 fctEnd.Set(nsfc: &rBySfc, nx: rBySfc.Wdt - iBorderWidth, ny: 0, nwdt: iBorderWidth, nhgt: iHeight);
99}
100
101void DynBarFacet::SetHorizontal(C4Facet &rByFct, int32_t iBorderWidth)
102{
103 if (!iBorderWidth) iBorderWidth = rByFct.Hgt;
104 fctBegin.Set(nsfc: rByFct.Surface, nx: rByFct.X, ny: rByFct.Y, nwdt: iBorderWidth, nhgt: rByFct.Hgt);
105 fctMiddle.Set(nsfc: rByFct.Surface, nx: rByFct.Hgt, ny: rByFct.X, nwdt: rByFct.Y + rByFct.Wdt - 2 * iBorderWidth, nhgt: rByFct.Hgt);
106 fctEnd.Set(nsfc: rByFct.Surface, nx: rByFct.X + rByFct.Wdt - iBorderWidth, ny: rByFct.Y, nwdt: iBorderWidth, nhgt: rByFct.Hgt);
107}
108
109void ScrollBarFacets::Set(const C4Facet &rByFct, int32_t iPinIndex)
110{
111 // set by hardcoded size
112 barScroll.fctBegin.Set(nsfc: rByFct.Surface, nx: 0, ny: 0, nwdt: 16, nhgt: 16);
113 barScroll.fctMiddle.Set(nsfc: rByFct.Surface, nx: 0, ny: 16, nwdt: 16, nhgt: 16);
114 barScroll.fctEnd.Set(nsfc: rByFct.Surface, nx: 0, ny: 32, nwdt: 16, nhgt: 16);
115 fctScrollDTop.Set(nsfc: rByFct.Surface, nx: 16, ny: 0, nwdt: 16, nhgt: 16);
116 if (iPinIndex)
117 fctScrollPin.Set(nsfc: rByFct.Surface, nx: 32, ny: 16 * (iPinIndex - 1), nwdt: 16, nhgt: 16);
118 else
119 fctScrollPin.Set(nsfc: rByFct.Surface, nx: 16, ny: 16, nwdt: 16, nhgt: 16);
120 fctScrollDBottom.Set(nsfc: rByFct.Surface, nx: 16, ny: 32, nwdt: 16, nhgt: 16);
121}
122
123// Element
124
125Element::Element() : pParent(nullptr), pDragTarget(nullptr), fDragging(false), pContextHandler(nullptr), fVisible(true)
126{
127 // pParent=nullptr invalidates pPrev/pNext
128 // fDragging=false invalidates iDragX/Y
129 // zero fields
130 rcBounds.Set(iX: 0, iY: 0, iWdt: 0, iHgt: 0);
131}
132
133Element::~Element()
134{
135 // delete context handler
136 if (pContextHandler) { pContextHandler->DeRef(); pContextHandler = nullptr; }
137 // remove from any container
138 if (pParent)
139 pParent->RemoveElement(pChild: this);
140 else if (this != Screen::GetScreenS())
141 // always ensure removal from screen!
142 if (Screen::GetScreenS())
143 Screen::GetScreenS()->RemoveElement(pChild: this);
144}
145
146void Element::RemoveElement(Element *pChild)
147{
148 // child removed: forward to parent
149 if (pParent)
150 pParent->RemoveElement(pChild);
151 else if (this != Screen::GetScreenS())
152 // always ensure removal from screen!
153 if (Screen::GetScreenS())
154 // but not if this is the context menu, to avoid endless flip-flop!
155 if (!IsMenu())
156 Screen::GetScreenS()->RemoveElement(pChild);
157}
158
159void Element::UpdateSize()
160{
161 // update own fields
162 UpdateOwnPos();
163 // notify container
164 if (pParent) pParent->ElementSizeChanged(pOfElement: this);
165}
166
167void Element::UpdatePos()
168{
169 // update own fields
170 UpdateOwnPos();
171 // notify container
172 if (pParent) pParent->ElementPosChanged(pOfElement: this);
173}
174
175bool Element::IsVisible()
176{
177 // self and parent must be visible
178 return fVisible && (!pParent || pParent->IsVisible());
179}
180
181void Element::SetVisibility(bool fToValue)
182{
183 fVisible = fToValue;
184 // stop mouseover for invisible
185 if (!fVisible)
186 {
187 Screen *pScreen = GetScreen();
188 if (pScreen) pScreen->Mouse.OnElementGetsInvisible(pChild: this);
189 }
190}
191
192void Element::ScreenPos2ClientPos(int32_t &riX, int32_t &riY)
193{
194 // apply all parent offsets
195 Container *pCont = pParent;
196 while (pCont)
197 {
198 pCont->ApplyElementOffset(riX, riY);
199 pCont = pCont->GetParent();
200 }
201 // apply own offset
202 riX -= rcBounds.x; riY -= rcBounds.y;
203}
204
205void Element::ClientPos2ScreenPos(int32_t &riX, int32_t &riY)
206{
207 // apply all parent offsets
208 Container *pCont = pParent;
209 while (pCont)
210 {
211 pCont->ApplyInvElementOffset(riX, riY);
212 pCont = pCont->GetParent();
213 }
214 // apply own offset
215 riX += rcBounds.x; riY += rcBounds.y;
216}
217
218void Element::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, uint32_t dwKeyParam)
219{
220 // store self as mouse-over-component
221 rMouse.pMouseOverElement = this;
222 // evaluate dragging
223 if (pDragTarget && iButton == C4MC_Button_LeftDown && !rMouse.pDragElement)
224 StartDragging(rMouse, iX, iY, dwKeyParam);
225 // right button down: open context menu
226 if (iButton == C4MC_Button_RightDown)
227 {
228 ContextHandler *pCtx = GetContextHandler();
229 if (pCtx) pCtx->OnContext(pOnElement: this, iX, iY);
230 }
231}
232
233void Element::StartDragging(CMouse &rMouse, int32_t iX, int32_t iY, uint32_t dwKeyParam)
234{
235 // set flag
236 fDragging = true;
237 // set drag start pos
238 iDragX = iX; iDragY = iY;
239 // mark drag in mouse
240 rMouse.pDragElement = this;
241}
242
243void Element::DoDragging(CMouse &rMouse, int32_t iX, int32_t iY, uint32_t dwKeyParam)
244{
245 // check if anything moved
246 if (pDragTarget && (iX != iDragX || iY != iDragY))
247 {
248 // move position, then
249 pDragTarget->rcBounds.x += iX - iDragX;
250 pDragTarget->rcBounds.y += iY - iDragY;
251 pDragTarget->UpdatePos();
252 }
253}
254
255void Element::StopDragging(CMouse &rMouse, int32_t iX, int32_t iY, uint32_t dwKeyParam)
256{
257 // move element pos
258 DoDragging(rMouse, iX, iY, dwKeyParam);
259}
260
261Dialog *Element::GetDlg() { if (pParent) return pParent->GetDlg(); return nullptr; }
262Screen *Element::GetScreen() { if (pParent) return pParent->GetScreen(); return nullptr; }
263
264void Element::Draw3DFrame(C4FacetEx &cgo, bool fUp, int32_t iIndent, uint8_t byAlpha, bool fDrawTop, int32_t iTopOff, bool fDrawLeft, int32_t iLeftOff)
265{
266 uint32_t dwAlpha = byAlpha << 24;
267 int32_t x0 = cgo.TargetX + rcBounds.x + iLeftOff,
268 y0 = cgo.TargetY + rcBounds.y + iTopOff,
269 x1 = cgo.TargetX + rcBounds.x + rcBounds.Wdt - 1,
270 y1 = cgo.TargetY + rcBounds.y + rcBounds.Hgt - 1;
271 if (fDrawTop) lpDDraw->DrawLineDw(sfcTarget: cgo.Surface, x1: static_cast<float>(x0), y1: static_cast<float>(y0), x2: static_cast<float>(x1), y2: static_cast<float>(y0), C4GUI_BorderColor1 | dwAlpha);
272 if (fDrawLeft) lpDDraw->DrawLineDw(sfcTarget: cgo.Surface, x1: static_cast<float>(x0), y1: static_cast<float>(y0), x2: static_cast<float>(x0), y2: static_cast<float>(y1), C4GUI_BorderColor1 | dwAlpha);
273 if (fDrawTop) lpDDraw->DrawLineDw(sfcTarget: cgo.Surface, x1: static_cast<float>(x0 + 1), y1: static_cast<float>(y0 + 1), x2: static_cast<float>(x1 - 1), y2: static_cast<float>(y0 + 1), C4GUI_BorderColor2 | dwAlpha);
274 if (fDrawLeft) lpDDraw->DrawLineDw(sfcTarget: cgo.Surface, x1: static_cast<float>(x0 + 1), y1: static_cast<float>(y0 + 1), x2: static_cast<float>(x0 + 1), y2: static_cast<float>(y1 - 1), C4GUI_BorderColor2 | dwAlpha);
275 lpDDraw->DrawLineDw(sfcTarget: cgo.Surface, x1: static_cast<float>(x0), y1: static_cast<float>(y1), x2: static_cast<float>(x1), y2: static_cast<float>(y1), C4GUI_BorderColor3 | dwAlpha);
276 lpDDraw->DrawLineDw(sfcTarget: cgo.Surface, x1: static_cast<float>(x1), y1: static_cast<float>(y0), x2: static_cast<float>(x1), y2: static_cast<float>(y1), C4GUI_BorderColor3 | dwAlpha);
277 lpDDraw->DrawLineDw(sfcTarget: cgo.Surface, x1: static_cast<float>(x0 + 1), y1: static_cast<float>(y1 - 1), x2: static_cast<float>(x1 - 1), y2: static_cast<float>(y1 - 1), C4GUI_BorderColor1 | dwAlpha);
278 lpDDraw->DrawLineDw(sfcTarget: cgo.Surface, x1: static_cast<float>(x1 - 1), y1: static_cast<float>(y0 + 1), x2: static_cast<float>(x1 - 1), y2: static_cast<float>(y1 - 1), C4GUI_BorderColor1 | dwAlpha);
279}
280
281void Element::DrawBar(C4FacetEx &cgo, DynBarFacet &rFacets)
282{
283 if (rcBounds.Hgt == rFacets.fctMiddle.Hgt)
284 {
285 // exact bar
286 int32_t x0 = cgo.TargetX + rcBounds.x, y0 = cgo.TargetY + rcBounds.y;
287 int32_t iX = rFacets.fctBegin.Wdt, w = rFacets.fctMiddle.Wdt, wLeft = rFacets.fctBegin.Wdt, wRight = rFacets.fctEnd.Wdt;
288 int32_t iRightShowLength = wRight / 3;
289 bool fOverflow = (wLeft > rcBounds.Wdt);
290 if (fOverflow) rFacets.fctBegin.Wdt = rcBounds.Wdt;
291 rFacets.fctBegin.Draw(sfcTarget: cgo.Surface, iX: x0, iY: y0);
292 if (fOverflow) rFacets.fctBegin.Wdt = wLeft;
293 while (iX < rcBounds.Wdt - iRightShowLength)
294 {
295 int32_t w2 = (std::min)(a: w, b: rcBounds.Wdt - iRightShowLength - iX); rFacets.fctMiddle.Wdt = w2;
296 rFacets.fctMiddle.Draw(sfcTarget: cgo.Surface, iX: x0 + iX, iY: y0);
297 iX += w;
298 }
299 rFacets.fctMiddle.Wdt = w;
300 fOverflow = (wRight > rcBounds.Wdt);
301 if (fOverflow)
302 {
303 rFacets.fctEnd.X += wRight - rcBounds.Wdt;
304 rFacets.fctEnd.Wdt = rcBounds.Wdt;
305 }
306 rFacets.fctEnd.Draw(sfcTarget: cgo.Surface, iX: x0 + rcBounds.Wdt - rFacets.fctEnd.Wdt, iY: y0);
307 if (fOverflow)
308 {
309 rFacets.fctEnd.X -= wRight - rcBounds.Wdt;
310 rFacets.fctEnd.Wdt = wRight;
311 }
312 }
313 else
314 {
315 // zoomed bar
316 float fZoom = static_cast<float>(rcBounds.Hgt) / rFacets.fctMiddle.Hgt;
317 int32_t x0 = cgo.TargetX + rcBounds.x, y0 = cgo.TargetY + rcBounds.y;
318 int32_t iX = int32_t(fZoom * rFacets.fctBegin.Wdt), w = int32_t(fZoom * rFacets.fctMiddle.Wdt), wOld = rFacets.fctMiddle.Wdt;
319 int32_t iRightShowLength = rFacets.fctEnd.Wdt / 3;
320 rFacets.fctBegin.DrawX(sfcTarget: cgo.Surface, iX: x0, iY: y0, iWdt: int32_t(fZoom * rFacets.fctBegin.Wdt), iHgt: rcBounds.Hgt);
321 while (iX < rcBounds.Wdt - (fZoom * iRightShowLength))
322 {
323 int32_t w2 = std::min<int32_t>(a: w, b: rcBounds.Wdt - int32_t(fZoom * iRightShowLength) - iX); rFacets.fctMiddle.Wdt = long(float(w2) / fZoom);
324 rFacets.fctMiddle.DrawX(sfcTarget: cgo.Surface, iX: x0 + iX, iY: y0, iWdt: w2, iHgt: rcBounds.Hgt);
325 iX += w;
326 }
327 rFacets.fctMiddle.Wdt = wOld;
328 rFacets.fctEnd.DrawX(sfcTarget: cgo.Surface, iX: x0 + rcBounds.Wdt - int32_t(fZoom * rFacets.fctEnd.Wdt), iY: y0, iWdt: int32_t(fZoom * rFacets.fctEnd.Wdt), iHgt: rcBounds.Hgt);
329 }
330}
331
332void Element::DrawVBar(C4FacetEx &cgo, DynBarFacet &rFacets)
333{
334 int32_t y0 = cgo.TargetY + rcBounds.y, x0 = cgo.TargetX + rcBounds.x;
335 int32_t iY = rFacets.fctBegin.Hgt, h = rFacets.fctMiddle.Hgt;
336 rFacets.fctBegin.Draw(sfcTarget: cgo.Surface, iX: x0, iY: y0);
337 while (iY < rcBounds.Hgt - 5)
338 {
339 int32_t h2 = (std::min)(a: h, b: rcBounds.Hgt - 5 - iY); rFacets.fctMiddle.Hgt = h2;
340 rFacets.fctMiddle.Draw(sfcTarget: cgo.Surface, iX: x0, iY: y0 + iY);
341 iY += h;
342 }
343 rFacets.fctMiddle.Hgt = h;
344 rFacets.fctEnd.Draw(sfcTarget: cgo.Surface, iX: x0, iY: y0 + rcBounds.Hgt - rFacets.fctEnd.Hgt);
345}
346
347void Element::DrawHBarByVGfx(C4FacetEx &cgo, DynBarFacet &rFacets)
348{
349 int32_t y0 = cgo.TargetY + rcBounds.y, x0 = cgo.TargetX + rcBounds.x;
350 int32_t iY = rFacets.fctBegin.Hgt, h = rFacets.fctMiddle.Hgt;
351 C4DrawTransform trf; trf.SetRotate(iAngle: -90 * 100, fOffX: cgo.TargetX + rcBounds.x + rcBounds.Hgt / 2.f, fOffY: cgo.TargetY + rcBounds.y + rcBounds.Hgt / 2.f);
352 rFacets.fctBegin.DrawT(sfcTarget: cgo.Surface, iX: x0, iY: y0, iPhaseX: 0, iPhaseY: 0, pTransform: &trf);
353 while (iY < rcBounds.Wdt - 5)
354 {
355 int32_t h2 = (std::min)(a: h, b: rcBounds.Wdt - 5 - iY); rFacets.fctMiddle.Hgt = h2;
356 rFacets.fctMiddle.DrawT(sfcTarget: cgo.Surface, iX: x0, iY: y0 + iY, iPhaseX: 0, iPhaseY: 0, pTransform: &trf);
357 iY += h;
358 }
359 rFacets.fctMiddle.Hgt = h;
360 rFacets.fctEnd.DrawT(sfcTarget: cgo.Surface, iX: x0, iY: y0 + rcBounds.Wdt - rFacets.fctEnd.Hgt, iPhaseX: 0, iPhaseY: 0, pTransform: &trf);
361}
362
363C4Rect Element::GetToprightCornerRect(int32_t iWidth, int32_t iHeight, int32_t iHIndent, int32_t iVIndent, int32_t iIndexX)
364{
365 // bounds by topright corner of element
366 C4Rect rtBounds = (GetContainer() != this) ? GetClientRect() : GetContainedClientRect();
367 rtBounds.x += rtBounds.Wdt - (iWidth + iHIndent) * (iIndexX + 1);
368 rtBounds.y += iVIndent;
369 rtBounds.Wdt = rtBounds.Hgt = iHeight;
370 return rtBounds;
371}
372
373void Element::SetToolTip(const char *szNewTooltip)
374{
375 // store tooltip
376 if (szNewTooltip) ToolTip.Copy(pnData: szNewTooltip); else ToolTip.Clear();
377}
378
379bool Element::DoContext()
380{
381 if (!pContextHandler) return false;
382 return pContextHandler->OnContext(pOnElement: this, iX: rcBounds.Wdt / 2, iY: rcBounds.Hgt / 2);
383}
384
385const char *Element::GetToolTip()
386{
387 // fallback to parent tooltip, if own is not assigned
388 return (!pParent || !ToolTip.isNull()) ? ToolTip.getData() : pParent->GetToolTip();
389}
390
391ContextHandler *Element::GetContextHandler()
392{
393 // fallback to parent context, if own is not assigned
394 return (!pParent || pContextHandler) ? pContextHandler : pParent->GetContextHandler();
395}
396
397bool Element::IsInActiveDlg(bool fForKeyboard)
398{
399 // get dlg
400 Dialog *pDlg = GetDlg();
401 if (!pDlg) return false;
402 // check if dlg is active
403 return pDlg->IsActive(fForKeyboard);
404}
405
406// CMouse
407
408CMouse::CMouse(int32_t iX, int32_t iY) : fActive(true), fActiveInput(false)
409{
410 // set pos
411 x = iX; y = iY;
412 // reset fields
413 LDown = MDown = RDown = false;
414 dwKeys = 0;
415 pMouseOverElement = pPrevMouseOverElement = nullptr;
416 pDragElement = nullptr;
417 ResetToolTipTime();
418 // LDownX/Y initialized upon need
419}
420
421CMouse::~CMouse() {}
422
423void CMouse::Input(int32_t iButton, int32_t iX, int32_t iY, uint32_t dwKeyParam)
424{
425 // pos changed or click issued?
426 if (iButton || iX != x || iY != y)
427 {
428 // then hide tooltips for a while
429 ResetToolTipTime();
430 // and mark as active input device
431 fActiveInput = true;
432 }
433 // copy fields
434 x = iX; y = iY; dwKeys = dwKeyParam;
435 // update buttons
436 switch (iButton)
437 {
438 case C4MC_Button_LeftDown: LDown = true; break;
439 case C4MC_Button_LeftUp: LDown = false; break;
440 case C4MC_Button_RightDown: RDown = true; break;
441 case C4MC_Button_RightUp: RDown = false; break;
442 }
443}
444
445void CMouse::Draw(C4FacetEx &cgo, bool fDrawToolTip)
446{
447 // only if owned
448 if (!fActive) return;
449
450 int32_t iOffsetX, iOffsetY;
451 if (GfxR->fOldStyleCursor)
452 {
453 iOffsetX = iOffsetY = 0;
454 }
455 else
456 {
457 iOffsetX = -GfxR->fctMouseCursor.Wdt / 2;
458 iOffsetY = -GfxR->fctMouseCursor.Hgt / 2;
459 }
460
461 const auto scale = Application.GetScale();
462 const auto inverseScale = 1 / scale;
463 C4DrawTransform transform;
464 transform.SetMoveScale(dx: 0.f, dy: 0.f, sx: inverseScale, sy: inverseScale);
465 GfxR->fctMouseCursor.DrawT(sfcTarget: cgo.Surface, iX: static_cast<int>(x * scale + iOffsetX), iY: static_cast<int>(y * scale + iOffsetY), iPhaseX: 0, iPhaseY: 0, pTransform: &transform, noScalingCorrection: true);
466 if (Game.MouseControl.IsHelp())
467 GfxR->fctMouseCursor.DrawT(sfcTarget: cgo.Surface, iX: static_cast<int>(x * scale + iOffsetX + 5), iY: static_cast<int>(y * scale + iOffsetY - 5), iPhaseX: 29, iPhaseY: 0, pTransform: &transform, noScalingCorrection: true);
468 // ToolTip
469 if (fDrawToolTip && pMouseOverElement)
470 {
471 const char *szTip = pMouseOverElement->GetToolTip();
472 if (szTip && *szTip)
473 {
474 C4FacetEx cgoTip; cgoTip.Set(nsfc: cgo.Surface, nx: cgo.X, ny: cgo.Y, nwdt: cgo.Wdt, nhgt: cgo.Hgt);
475 Screen::DrawToolTip(szTip, cgo&: cgoTip, x, y);
476 }
477 }
478}
479
480void CMouse::ReleaseElements()
481{
482 // release MouseOver
483 if (pMouseOverElement) pMouseOverElement->MouseLeave(rMouse&: *this);
484 // release drag
485 if (pDragElement)
486 {
487 int32_t iX, iY; uint32_t dwKeys;
488 GetLastXY(rX&: iX, rY&: iY, rdwKeys&: dwKeys);
489 pDragElement->ScreenPos2ClientPos(riX&: iX, riY&: iY);
490 pDragElement->StopDragging(rMouse&: *this, iX, iY, dwKeyParam: dwKeys);
491 }
492 pPrevMouseOverElement = pMouseOverElement = pDragElement = nullptr;
493}
494
495void CMouse::RemoveElement(Element *pChild)
496{
497 // clear ptr
498 if (pMouseOverElement == pChild)
499 {
500 pMouseOverElement->MouseLeave(rMouse&: *this); // do leave callback so any tooltip is cleared!
501 pMouseOverElement = nullptr;
502 }
503 if (pPrevMouseOverElement == pChild) pPrevMouseOverElement = nullptr;
504 if (pDragElement == pChild) pDragElement = nullptr;
505}
506
507void CMouse::OnElementGetsInvisible(Element *pChild)
508{
509 // clear ptr
510 RemoveElement(pChild);
511}
512
513// Screen
514
515void Screen::RemoveElement(Element *pChild)
516{
517 // inherited
518 Window::RemoveElement(pChild);
519 // clear ptrs
520 if (pActiveDlg == pChild) { pActiveDlg = nullptr; Mouse.ResetElements(); }
521 Mouse.RemoveElement(pChild);
522 if (pContext) if (pContext == pChild) pContext = nullptr; else pContext->RemoveElement(pChild);
523}
524
525Screen::Screen(int32_t tx, int32_t ty, int32_t twdt, int32_t thgt) : Window(), Mouse(tx + twdt / 2, ty + thgt / 2), pContext(nullptr), fExclusive(true), pGamePadOpener(nullptr)
526{
527 // no dialog active
528 pActiveDlg = nullptr;
529 // set size - calcs client area as well
530 SetBounds(C4Rect(tx, ty, twdt, thgt));
531 SetPreferredDlgRect(C4Rect(0, 0, twdt, thgt));
532 // set static var
533 pScreen = this;
534 // GamePad
535 if (Application.pGamePadControl && Config.Controls.GamepadGuiControl)
536 pGamePadOpener = new C4GamePadOpener(0);
537}
538
539Screen::~Screen()
540{
541 // dtor: Close context menu
542 AbortContext(fByUser: false);
543 // clear singleton
544 if (this == pScreen) pScreen = nullptr;
545 // GamePad
546 delete pGamePadOpener;
547}
548
549void Screen::ElementPosChanged(Element *pOfElement)
550{
551 // redraw fullscreen BG if dlgs are dragged around in shared mode
552 if (!IsExclusive())
553 Game.GraphicsSystem.InvalidateBg();
554}
555
556void Screen::ShowDialog(Dialog *pDlg, bool fFade)
557{
558 assert(pDlg);
559 // do place console mode dialogs
560 if (Application.isFullScreen || pDlg->IsViewportDialog())
561 // exclusive or free dlg: center pos
562 // evaluate own placement proc first
563 if (!pDlg->DoPlacement(pOnScreen: this, rPreferredDlgRect: PreferredDlgRect))
564 {
565 if (pDlg->IsFreePlaceDialog())
566 pDlg->SetPos(iXPos: (GetWidth() - pDlg->GetWidth()) / 2, iYPos: (GetHeight() - pDlg->GetHeight()) / 2 + pDlg->IsBottomPlacementDialog() * GetHeight() / 3);
567 else if (IsExclusive())
568 pDlg->SetPos(iXPos: (GetWidth() - pDlg->GetWidth()) / 2, iYPos: (GetHeight() - pDlg->GetHeight()) / 2);
569 else
570 // non-exclusive mode at preferred viewport pos
571 pDlg->SetPos(iXPos: PreferredDlgRect.x + 30, iYPos: PreferredDlgRect.y + 30);
572 }
573 // add to local component list at correct ordering
574 int32_t iNewZ = pDlg->GetZOrdering(); Element *pEl; Dialog *pOtherDlg;
575 for (pEl = GetFirst(); pEl; pEl = pEl->GetNext())
576 if (pOtherDlg = pEl->GetDlg())
577 if (pOtherDlg->GetZOrdering() > iNewZ)
578 break;
579 InsertElement(pChild: pDlg, pInsertBefore: pEl);
580 // set as active, if not fading and on top
581 if (!fFade && !pEl)
582 // but not viewport dialogs!
583 if (!pDlg->IsExternalDrawDialog())
584 pActiveDlg = pDlg;
585 // show it
586 pDlg->fOK = false;
587 pDlg->fShow = true;
588 // mouse focus might have changed
589 UpdateMouseFocus();
590}
591
592void Screen::ActivateDialog(Dialog *pDlg)
593{
594 // no change?
595 if (pActiveDlg == pDlg) return;
596 // in single-mode: release any MouseOver/Drag of previous dlg
597 if (IsExclusive())
598 Mouse.ReleaseElements();
599 // close any context menu
600 AbortContext(fByUser: false);
601 // set as active dlg
602 pActiveDlg = pDlg;
603 // ensure it's last in the list, if it's not a specially ordered dlg
604 if (!pDlg->GetZOrdering() && pDlg->GetNext())
605 MakeLastElement(pChild: pDlg);
606}
607
608void Screen::CloseDialog(Dialog *pDlg, bool fFade)
609{
610 // hide dlg
611 if (!fFade) pDlg->fShow = false;
612 // kill from active
613 if (pActiveDlg == pDlg)
614 {
615 // release any MouseOver/Drag of previous dlg
616 Mouse.ReleaseElements();
617 // close context menu: probably belonging to closed dlg anyway
618 AbortContext(fByUser: false);
619 // set new active dlg
620 pActiveDlg = GetTopDialog();
621 // do not set yet if it's fading
622 if (pActiveDlg && pActiveDlg->IsFading()) pActiveDlg = nullptr;
623 }
624 // redraw background; clip update
625 Game.GraphicsSystem.InvalidateBg(); UpdateMouseFocus();
626}
627
628void Screen::RecheckActiveDialog()
629{
630 Dialog *pNewTop = GetTopDialog();
631 if (pActiveDlg == pNewTop) return;
632 Mouse.ReleaseElements();
633 // do not set yet if it's fading
634 if (pActiveDlg && pActiveDlg->IsFading()) pActiveDlg = nullptr;
635}
636
637Dialog *Screen::GetTopDialog()
638{
639 // search backwards in component list
640 Dialog *pDlg;
641 for (Element *pEl = pLast; pEl; pEl = pEl->GetPrev())
642 if (pDlg = pEl->GetDlg())
643 if (pDlg->IsShown())
644 return pDlg;
645 // no dlg found
646 return nullptr;
647}
648
649void Screen::CloseAllDialogs(bool fWithOK)
650{
651 while (pActiveDlg) pActiveDlg->Close(fOK: fWithOK);
652}
653
654void Screen::Render(bool fDoBG)
655{
656 // get output cgo
657 C4FacetEx cgo;
658 cgo.Set(nsfc: lpDDraw->lpBack, nx: 0, ny: 0, nwdt: Config.Graphics.ResX, nhgt: Config.Graphics.ResY);
659 // draw to it
660 Draw(cgo, fDoBG);
661}
662
663void Screen::RenderMouse(C4FacetEx &cgo)
664{
665 // draw mouse cursor
666 Mouse.Draw(cgo, fDrawToolTip: (Mouse.IsMouseStill() && Mouse.IsActiveInput()) || Game.MouseControl.IsHelp());
667}
668
669void Screen::Draw(C4FacetEx &cgo, bool fDoBG)
670{
671 // draw bg, if this won't be done by a fullscreen dialog
672 if (fDoBG)
673 {
674 Dialog *pFSDlg = GetFullscreenDialog(fIncludeFading: false);
675 if (!pFSDlg || !pFSDlg->HasBackground())
676 {
677 if (Game.GraphicsSystem.pLoaderScreen)
678 Game.GraphicsSystem.pLoaderScreen->fctBackground.DrawFullScreen(cgo);
679 else
680 // loader not yet loaded: black BG
681 lpDDraw->DrawBoxDw(sfcDest: cgo.Surface, iX1: 0, iY1: 0, iX2: cgo.Wdt + 1, iY2: cgo.Hgt + 1, dwClr: 0x00000000);
682 }
683 }
684 // draw contents (if GUI-gfx are loaded, which is assumed in GUI-drawing-functions)
685 if (IsVisible() && IsResLoaded())
686 {
687 Window::Draw(cgo);
688 if (pContext) pContext->Draw(cgo);
689 }
690 // draw mouse cursor
691 if (Application.isFullScreen) RenderMouse(cgo);
692}
693
694bool Screen::KeyAny()
695{
696 // mark keystroke in mouse
697 Mouse.ResetActiveInput();
698 // key not yet processed
699 return false;
700}
701
702bool Screen::CharIn(const char *c)
703{
704 // Special: Tab chars are ignored, because they are always handled as focus advance
705 if (c[0] == 0x09) return false;
706 // mark in mouse
707 Mouse.ResetActiveInput();
708 // no processing if focus is not set
709 if (!HasKeyboardFocus()) return false;
710 // always return true in exclusive mode (which means: key processed)
711 bool fResult = IsExclusive();
712 // context menu: forward to context
713 if (pContext) return pContext->CharIn(c) || fResult;
714 // no active dlg?
715 if (!pActiveDlg || !pActiveDlg->IsVisible()) return fResult;
716 // forward to dialog
717 return pActiveDlg->CharIn(c) || fResult;
718}
719
720bool Screen::MouseInput(int32_t iButton, int32_t iX, int32_t iY, uint32_t dwKeyParam, Dialog *pForDlg, class C4Viewport *pForVP)
721{
722 // help mode and button pressed: Abort help and discard button
723 if (Game.MouseControl.IsHelp())
724 {
725 switch (iButton)
726 {
727 case C4MC_Button_None:
728 // just movement
729 break;
730 case C4MC_Button_LeftDown:
731 case C4MC_Button_RightDown:
732 // special for left/right down: Just ignore them, but don't stop help yet
733 // help should be stopped on button-up, so these won't be processed
734 iButton = C4MC_Button_None;
735 break;
736 default:
737 // buttons stop help
738 Game.MouseControl.AbortHelp();
739 iButton = C4MC_Button_None;
740 break;
741 }
742 }
743 // forward to mouse
744 Mouse.Input(iButton, iX, iY, dwKeyParam);
745 // dragging
746 if (Mouse.pDragElement)
747 {
748 int32_t iX2 = iX, iY2 = iY;
749 Mouse.pDragElement->ScreenPos2ClientPos(riX&: iX2, riY&: iY2);
750 if (!Mouse.IsLDown())
751 {
752 // stop dragging
753 Mouse.pDragElement->StopDragging(rMouse&: Mouse, iX: iX2, iY: iY2, dwKeyParam);
754 Mouse.pDragElement = nullptr;
755 }
756 else
757 {
758 // continue dragging
759 Mouse.pDragElement->DoDragging(rMouse&: Mouse, iX: iX2, iY: iY2, dwKeyParam);
760 }
761 }
762 // backup previous MouseOver-element
763 Mouse.pPrevMouseOverElement = Mouse.pMouseOverElement;
764 Mouse.pMouseOverElement = nullptr;
765 bool fProcessed = false;
766 // active context menu?
767 if (!pForVP && pContext && pContext->CtxMouseInput(rMouse&: Mouse, iButton, iScreenX: iX, iScreenY: iY, dwKeyParam))
768 {
769 // processed by context menu: OK!
770 }
771 // otherwise: active dlg and inside screen? (or direct forward to specific dlg/viewport dlg)
772 else if (rcBounds.Contains(iX, iY) || pForDlg || pForVP)
773 {
774 // context menu open but mouse down command issued? close context then
775 if (pContext && (iButton == C4MC_Button_LeftDown || iButton == C4MC_Button_RightDown))
776 AbortContext(fByUser: true);
777 // get client pos
778 if (!pForDlg && !pForVP)
779 {
780 C4Rect &rcClientArea = GetClientRect();
781 iX -= rcClientArea.x; iY -= rcClientArea.y;
782 }
783 // exclusive mode: process active dialog only
784 if (IsExclusive() && !pForDlg && !pForVP)
785 {
786 if (pActiveDlg && pActiveDlg->IsVisible() && !pActiveDlg->IsFading())
787 {
788 // bounds check to dlg: only if not dragging
789 C4Rect &rcDlgBounds = pActiveDlg->GetBounds();
790 if (Mouse.IsLDown() || rcDlgBounds.Contains(iX, iY))
791 // forward to active dialog
792 pActiveDlg->MouseInput(rMouse&: Mouse, iButton, iX: iX - rcDlgBounds.x, iY: iY - rcDlgBounds.y, dwKeyParam);
793 else
794 Mouse.pMouseOverElement = nullptr;
795 }
796 else
797 // outside dialog: own handling (for screen context menu)
798 Window::MouseInput(rMouse&: Mouse, iButton, iX, iY, dwKeyParam);
799 }
800 else
801 {
802 // non-exclusive mode: process all dialogs; make them active on left-click
803 Dialog *pDlg;
804 for (Element *pEl = pLast; pEl; pEl = pEl->GetPrev())
805 if (pDlg = pEl->GetDlg())
806 if (pDlg->IsShown())
807 {
808 // if specified: process specified dlg only
809 if (pForDlg && pDlg != pForDlg) continue;
810 // if specified: process specified viewport only
811 bool fIsExternalDrawDialog = pDlg->IsExternalDrawDialog();
812 C4Viewport *pVP = fIsExternalDrawDialog ? pDlg->GetViewport() : nullptr;
813 if (pForVP && pForVP != pVP) continue;
814 // calc offset
815 C4Rect &rcDlgBounds = pDlg->GetBounds();
816 int32_t iOffX = 0, iOffY = 0;
817 // special handling for viewport dialogs
818 if (fIsExternalDrawDialog)
819 {
820 // ignore external drawing dialogs without a viepwort assigned
821 if (!pVP) continue;
822 // always clip to viewport bounds
823 C4Rect rcOut(pVP->GetOutputRect());
824 if (!rcOut.Contains(iX: iX + rcBounds.x, iY: iY + rcBounds.y)) continue;
825 // viewport dialogs: Offset determined by viewport position
826 iOffX = rcOut.x; iOffY = rcOut.y;
827 }
828 // hit test; or special: dragging possible outside active dialog
829 if (rcDlgBounds.Contains(iX: iX - iOffX, iY: iY - iOffY) || (pDlg == pActiveDlg && Mouse.pDragElement))
830 {
831 // Okay; do input
832 pDlg->MouseInput(rMouse&: Mouse, iButton, iX: iX - rcDlgBounds.x - iOffX, iY: iY - rcDlgBounds.y - iOffY, dwKeyParam);
833 // dlgs may destroy GUI
834 if (!IsGUIValid()) return false;
835 // CAUTION: pDlg may be invalid now!
836 // set processed-flag manually
837 fProcessed = true;
838 // inactive dialogs get activated by clicks
839 if (Mouse.IsLDown() && pDlg != pActiveDlg)
840 // but not viewport dialogs!
841 if (!pDlg->IsExternalDrawDialog())
842 ActivateDialog(pDlg);
843 // one dlg only; break loop here
844 break;
845 }
846 }
847 }
848 // check valid GUI; might be destroyed by mouse input
849 if (!IsGUIValid()) return false;
850 }
851
852 // check if MouseOver has changed
853 if (Mouse.pPrevMouseOverElement != Mouse.pMouseOverElement)
854 {
855 // send events
856 if (Mouse.pPrevMouseOverElement) Mouse.pPrevMouseOverElement->MouseLeave(rMouse&: Mouse);
857 if (Mouse.pMouseOverElement) Mouse.pMouseOverElement->MouseEnter(rMouse&: Mouse);
858 }
859 // return whether anything processed it
860 return fProcessed || Mouse.pDragElement || (Mouse.pMouseOverElement && Mouse.pMouseOverElement != this) || pContext;
861}
862
863void Screen::UpdateMouseFocus()
864{
865 // when exclusive mode has changed: Make sure mouse clip is correct
866 Game.MouseControl.UpdateClip();
867}
868
869void Screen::DoContext(ContextMenu *pNewCtx, Element *pAtElement, int32_t iX, int32_t iY)
870{
871 assert(pNewCtx); assert(pNewCtx != pContext);
872 // close previous context menu
873 AbortContext(fByUser: false);
874 // element offset
875 if (pAtElement) pAtElement->ClientPos2ScreenPos(riX&: iX, riY&: iY);
876 // usually open bottom right
877 // check bottom bounds
878 if (iY + pNewCtx->GetBounds().Hgt >= GetBounds().Hgt)
879 {
880 // bottom too narrow: open to top, if height is sufficient
881 // otherwise, open to top from bottom screen pos
882 if (iY < pNewCtx->GetBounds().Hgt) iY = GetBounds().Hgt;
883 iY -= pNewCtx->GetBounds().Hgt;
884 }
885 // check right bounds likewise
886 if (iX + pNewCtx->GetBounds().Wdt >= GetBounds().Wdt)
887 {
888 // bottom too narrow: open to top, if height is sufficient
889 // otherwise, open to top from bottom screen pos
890 if (iX < pNewCtx->GetBounds().Wdt) iX = GetBounds().Wdt;
891 iX -= pNewCtx->GetBounds().Wdt;
892 }
893 // open new
894 (pContext = pNewCtx)->Open(pTarget: pAtElement, iScreenX: iX, iScreenY: iY);
895}
896
897int32_t Screen::GetMouseControlledDialogCount()
898{
899 Dialog *pDlg; int32_t iResult = 0;
900 for (Element *pEl = GetFirst(); pEl; pEl = pEl->GetNext())
901 if (pDlg = pEl->GetDlg())
902 if (pDlg->IsShown() && pDlg->IsMouseControlled())
903 ++iResult;
904 return iResult;
905}
906
907void Screen::DrawToolTip(const char *szTip, C4FacetEx &cgo, int32_t x, int32_t y)
908{
909 CStdFont *pUseFont = &(GetRes()->TooltipFont);
910 StdStrBuf sText;
911 pUseFont->BreakMessage(szMsg: szTip, iWdt: std::min<int32_t>(C4GUI_MaxToolTipWdt, b: std::max<int32_t>(a: cgo.Wdt, b: 50)), pOut: &sText, fCheckMarkup: true);
912 // get tooltip rect
913 int32_t tWdt, tHgt;
914 if (pUseFont->GetTextExtent(szText: sText.getData(), rsx&: tWdt, rsy&: tHgt, fCheckMarkup: true))
915 {
916 tWdt += 6; tHgt += 4;
917 int32_t tX, tY;
918 if (y < cgo.Y + cgo.TargetY + tHgt + 5) tY = std::min<int32_t>(a: y + 5, b: cgo.TargetY + cgo.Hgt - tHgt); else tY = y - tHgt - 5;
919 tX = BoundBy<int32_t>(bval: x - tWdt / 2, lbound: cgo.TargetX + cgo.X, rbound: cgo.TargetX + cgo.Wdt - tWdt);
920 // draw tooltip box
921 lpDDraw->DrawBoxDw(sfcDest: cgo.Surface, iX1: tX, iY1: tY, iX2: tX + tWdt - 1, iY2: tY + tHgt - 2, C4GUI_ToolTipBGColor);
922 lpDDraw->DrawFrameDw(sfcDest: cgo.Surface, x1: tX, y1: tY, x2: tX + tWdt - 1, y2: tY + tHgt - 1, C4GUI_ToolTipFrameColor);
923 // draw tooltip
924 lpDDraw->TextOut(szText: sText.getData(), rFont&: *pUseFont, fZoom: 1.0f, sfcDest: cgo.Surface, iTx: tX + 3, iTy: tY + 1, C4GUI_ToolTipColor, byForm: ALeft);
925 // while there's a tooltip, redraw the bg, because it might overlap
926 Game.GraphicsSystem.InvalidateBg();
927 }
928}
929
930bool Screen::HasFullscreenDialog(bool fIncludeFading)
931{
932 return !!GetFullscreenDialog(fIncludeFading);
933}
934
935Dialog *Screen::GetFullscreenDialog(bool fIncludeFading)
936{
937 Dialog *pDlg;
938 for (Element *pEl = GetFirst(); pEl; pEl = pEl->GetNext())
939 if (pDlg = pEl->GetDlg())
940 if (pDlg->IsVisible())
941 if (pDlg->IsFullscreenDialog())
942 if (fIncludeFading || !pDlg->IsFading())
943 return pDlg;
944 return nullptr;
945}
946
947bool Screen::HasKeyboardFocus()
948{
949 // always hook keyboard in exclusive mode; only on exclusive top dialogs in shared mode
950 if (IsExclusive()) return true;
951 Dialog *pDlg = GetTopDialog();
952 return pDlg && pDlg->IsExclusiveDialog();
953}
954
955bool Screen::HasMouseFocus()
956{
957 return HasKeyboardFocus();
958}
959
960void Screen::UpdateGamepadGUIControlEnabled()
961{
962 // update pGamePadOpener to config value
963 if (pGamePadOpener && (!Config.Controls.GamepadGuiControl || !Application.pGamePadControl))
964 {
965 delete pGamePadOpener; pGamePadOpener = nullptr;
966 }
967 else if (!pGamePadOpener && (Config.Controls.GamepadGuiControl && Application.pGamePadControl))
968 {
969 pGamePadOpener = new C4GamePadOpener(0);
970 }
971}
972
973// ComponentAligner
974
975bool ComponentAligner::GetFromTop(int32_t iHgt, int32_t iWdt, C4Rect &rcOut)
976{
977 rcOut.x = rcClientArea.x + iMarginX;
978 rcOut.y = rcClientArea.y + iMarginY;
979 rcOut.Wdt = rcClientArea.Wdt - iMarginX * 2;
980 rcOut.Hgt = iHgt;
981 int32_t d = iHgt + iMarginY * 2;
982 rcClientArea.y += d; rcClientArea.Hgt -= d;
983 // get centered in width as specified
984 if (iWdt >= 0)
985 {
986 rcOut.x += (rcOut.Wdt - iWdt) / 2;
987 rcOut.Wdt = iWdt;
988 }
989 return rcClientArea.Hgt >= 0;
990}
991
992bool ComponentAligner::GetFromLeft(int32_t iWdt, int32_t iHgt, C4Rect &rcOut)
993{
994 rcOut.x = rcClientArea.x + iMarginX;
995 rcOut.y = rcClientArea.y + iMarginY;
996 rcOut.Wdt = iWdt;
997 rcOut.Hgt = rcClientArea.Hgt - iMarginY * 2;
998 int32_t d = iWdt + iMarginX * 2;
999 rcClientArea.x += d; rcClientArea.Wdt -= d;
1000 // get centered in height as specified
1001 if (iHgt >= 0)
1002 {
1003 rcOut.y += (rcOut.Hgt - iHgt) / 2;
1004 rcOut.Hgt = iHgt;
1005 }
1006 return rcClientArea.Wdt >= 0;
1007}
1008
1009bool ComponentAligner::GetFromRight(int32_t iWdt, int32_t iHgt, C4Rect &rcOut)
1010{
1011 rcOut.x = rcClientArea.x + rcClientArea.Wdt - iWdt - iMarginX;
1012 rcOut.y = rcClientArea.y + iMarginY;
1013 rcOut.Wdt = iWdt;
1014 rcOut.Hgt = rcClientArea.Hgt - iMarginY * 2;
1015 rcClientArea.Wdt -= iWdt + iMarginX * 2;
1016 // get centered in height as specified
1017 if (iHgt >= 0)
1018 {
1019 rcOut.y += (rcOut.Hgt - iHgt) / 2;
1020 rcOut.Hgt = iHgt;
1021 }
1022 return rcClientArea.Wdt >= 0;
1023}
1024
1025bool ComponentAligner::GetFromBottom(int32_t iHgt, int32_t iWdt, C4Rect &rcOut)
1026{
1027 rcOut.x = rcClientArea.x + iMarginX;
1028 rcOut.y = rcClientArea.y + rcClientArea.Hgt - iHgt - iMarginY;
1029 rcOut.Wdt = rcClientArea.Wdt - iMarginX * 2;
1030 rcOut.Hgt = iHgt;
1031 rcClientArea.Hgt -= iHgt + iMarginY * 2;
1032 // get centered in width as specified
1033 if (iWdt >= 0)
1034 {
1035 rcOut.x += (rcOut.Wdt - iWdt) / 2;
1036 rcOut.Wdt = iWdt;
1037 }
1038 return rcClientArea.Hgt >= 0;
1039}
1040
1041void ComponentAligner::GetAll(C4Rect &rcOut)
1042{
1043 rcOut.x = rcClientArea.x + iMarginX;
1044 rcOut.y = rcClientArea.y + iMarginY;
1045 rcOut.Wdt = rcClientArea.Wdt - iMarginX * 2;
1046 rcOut.Hgt = rcClientArea.Hgt - iMarginY * 2;
1047}
1048
1049bool ComponentAligner::GetCentered(int32_t iWdt, int32_t iHgt, C4Rect &rcOut)
1050{
1051 rcOut.x = rcClientArea.GetMiddleX() - iWdt / 2;
1052 rcOut.y = rcClientArea.GetMiddleY() - iHgt / 2;
1053 rcOut.Wdt = iWdt;
1054 rcOut.Hgt = iHgt;
1055 // range check
1056 return rcOut.Wdt + iMarginX * 2 <= rcClientArea.Wdt && rcOut.Hgt + iMarginY * 2 <= rcClientArea.Hgt;
1057}
1058
1059C4Rect &ComponentAligner::GetGridCell(int32_t iSectX, int32_t iSectXMax, int32_t iSectY, int32_t iSectYMax, int32_t iSectSizeX, int32_t iSectSizeY, bool fCenterPos, int32_t iSectNumX, int32_t iSectNumY)
1060{
1061 int32_t iSectSizeXO = iSectSizeX, iSectSizeYO = iSectSizeY;
1062 int32_t iSectSizeXMax = (rcClientArea.Wdt - iMarginX) / iSectXMax - iMarginX;
1063 int32_t iSectSizeYMax = (rcClientArea.Hgt - iMarginY) / iSectYMax - iMarginY;
1064 if (iSectSizeX < 0 || fCenterPos) iSectSizeX = iSectSizeXMax; else iSectSizeX = std::min<int32_t>(a: iSectSizeX, b: iSectSizeXMax);
1065 if (iSectSizeY < 0 || fCenterPos) iSectSizeY = iSectSizeYMax; else iSectSizeY = std::min<int32_t>(a: iSectSizeY, b: iSectSizeYMax);
1066 rcTemp.x = iSectX * (iSectSizeX + iMarginX) + iMarginX + rcClientArea.x;
1067 rcTemp.y = iSectY * (iSectSizeY + iMarginY) + iMarginY + rcClientArea.y;
1068 rcTemp.Wdt = iSectSizeX * iSectNumX + iMarginX * (iSectNumX - 1); rcTemp.Hgt = iSectSizeY * iSectNumY + iMarginY * (iSectNumY - 1);
1069 if (iSectSizeXO >= 0 && fCenterPos)
1070 {
1071 rcTemp.x += (iSectSizeX - iSectSizeXO) / 2;
1072 rcTemp.Wdt = iSectSizeXO;
1073 }
1074 if (iSectSizeYO >= 0 && fCenterPos)
1075 {
1076 rcTemp.y += (iSectSizeY - iSectSizeYO) / 2;
1077 rcTemp.Hgt = iSectSizeYO;
1078 }
1079 return rcTemp;
1080}
1081
1082// Resource
1083
1084bool Resource::Load(C4GroupSet &rFromGroup)
1085{
1086 // load gfx - using helper funcs from Game.GraphicsResource here...
1087 if (!Game.GraphicsResource.LoadFile(sfc&: sfcCaption, szName: "GUICaption", rGfxSet&: rFromGroup, ridCurrSfc&: idSfcCaption)) return false;
1088 barCaption.SetHorizontal(rBySfc&: sfcCaption, iHeight: sfcCaption.Hgt, iBorderWidth: 32);
1089 if (!Game.GraphicsResource.LoadFile(sfc&: sfcButton, szName: "GUIButton", rGfxSet&: rFromGroup, ridCurrSfc&: idSfcButton)) return false;
1090 barButton.SetHorizontal(rBySfc&: sfcButton);
1091 if (!Game.GraphicsResource.LoadFile(sfc&: sfcButtonD, szName: "GUIButtonDown", rGfxSet&: rFromGroup, ridCurrSfc&: idSfcButtonD)) return false;
1092 barButtonD.SetHorizontal(rBySfc&: sfcButtonD);
1093 if (!Game.GraphicsResource.LoadFile(fct&: fctButtonHighlight, szName: "GUIButtonHighlight", rGfxSet&: rFromGroup)) return false;
1094 if (!Game.GraphicsResource.LoadFile(fct&: fctIcons, szName: "GUIIcons", rGfxSet&: rFromGroup)) return false;
1095 fctIcons.Set(nsfc: fctIcons.Surface, nx: 0, ny: 0, C4GUI_IconWdt, C4GUI_IconHgt);
1096 if (!Game.GraphicsResource.LoadFile(fct&: fctIconsEx, szName: "GUIIcons2", rGfxSet&: rFromGroup)) return false;
1097 fctIconsEx.Set(nsfc: fctIconsEx.Surface, nx: 0, ny: 0, C4GUI_IconExWdt, C4GUI_IconExHgt);
1098 if (!Game.GraphicsResource.LoadFile(sfc&: sfcScroll, szName: "GUIScroll", rGfxSet&: rFromGroup, ridCurrSfc&: idSfcScroll)) return false;
1099 sfctScroll.Set(rByFct: C4Facet(&sfcScroll, 0, 0, 32, 32));
1100 if (!Game.GraphicsResource.LoadFile(sfc&: sfcContext, szName: "GUIContext", rGfxSet&: rFromGroup, ridCurrSfc&: idSfcContext)) return false;
1101 fctContext.Set(nsfc: &sfcContext, nx: 0, ny: 0, nwdt: 16, nhgt: 16);
1102 if (!Game.GraphicsResource.LoadFile(fct&: fctSubmenu, szName: "GUISubmenu", rGfxSet&: rFromGroup)) return false;
1103 if (!Game.GraphicsResource.LoadFile(fct&: fctCheckbox, szName: "GUICheckbox", rGfxSet&: rFromGroup)) return false;
1104 fctCheckbox.Set(nsfc: fctCheckbox.Surface, nx: 0, ny: 0, nwdt: fctCheckbox.Hgt, nhgt: fctCheckbox.Hgt);
1105 if (!Game.GraphicsResource.LoadFile(fct&: fctBigArrows, szName: "GUIBigArrows", rGfxSet&: rFromGroup)) return false;
1106 fctBigArrows.Set(nsfc: fctBigArrows.Surface, nx: 0, ny: 0, nwdt: fctBigArrows.Wdt / 4, nhgt: fctBigArrows.Hgt);
1107 if (!Game.GraphicsResource.LoadFile(fct&: fctProgressBar, szName: "GUIProgress", rGfxSet&: rFromGroup)) return false;
1108 fctProgressBar.Set(nsfc: fctProgressBar.Surface, nx: 1, ny: 0, nwdt: fctProgressBar.Wdt - 2, nhgt: fctProgressBar.Hgt);
1109 if (!Game.GraphicsResource.LoadFile(fct&: fctSpinBoxArrow, szName: "GUISpinBoxArrow", rGfxSet&: rFromGroup)) return false;
1110 // loaded sucessfully
1111 pRes = this;
1112 return true;
1113}
1114
1115void Resource::Clear()
1116{
1117 // clear surfaces
1118 sfcCaption.Clear(); sfcButton.Clear(); sfcButtonD.Clear(); sfcScroll.Clear(); sfcContext.Clear();
1119 idSfcCaption = idSfcButton = idSfcButtonD = idSfcScroll = idSfcContext = 0;
1120 barCaption.Clear(); barButton.Clear(); barButtonD.Clear();
1121 fctButtonHighlight.Clear(); fctIcons.Clear(); fctIconsEx.Clear();
1122 fctSubmenu.Clear();
1123 fctCheckbox.Clear();
1124 fctBigArrows.Clear();
1125 fctProgressBar.Clear();
1126 fctContext.Default();
1127 // facets are invalid now...doesn't matter anyway, as long as res ptr is not set to this class
1128 if (pRes == this) pRes = nullptr;
1129}
1130
1131CStdFont &Resource::GetFontByHeight(int32_t iHgt, float *pfZoom)
1132{
1133 // get optimal font for given control size
1134 CStdFont *pUseFont;
1135 if (iHgt <= MiniFont.GetLineHeight()) pUseFont = &MiniFont;
1136 else if (iHgt <= TextFont.GetLineHeight()) pUseFont = &TextFont;
1137 else if (iHgt <= CaptionFont.GetLineHeight()) pUseFont = &CaptionFont;
1138 else pUseFont = &TitleFont;
1139 // determine zoom
1140 if (pfZoom)
1141 {
1142 int32_t iLineHgt = pUseFont->GetLineHeight();
1143 if (iLineHgt)
1144 *pfZoom = static_cast<float>(iHgt) / static_cast<float>(iLineHgt);
1145 else
1146 *pfZoom = 1.0f; // error
1147 }
1148 return *pUseFont;
1149}
1150
1151// Global stuff
1152
1153int32_t GetScreenWdt()
1154{
1155 Screen *pScreen = Screen::GetScreenS();
1156 return pScreen ? pScreen->GetBounds().Wdt : Config.Graphics.ResX;
1157}
1158
1159int32_t GetScreenHgt()
1160{
1161 Screen *pScreen = Screen::GetScreenS();
1162 return pScreen ? pScreen->GetBounds().Hgt : Config.Graphics.ResY;
1163}
1164
1165void GUISound(const char *szSound)
1166{
1167 if (Config.Sound.FESamples)
1168 StartSoundEffect(name: szSound);
1169}
1170
1171// Static vars
1172
1173C4Rect ComponentAligner::rcTemp;
1174Resource *Resource::pRes;
1175Screen *Screen::pScreen;
1176} // namespace C4GUI
1177