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