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 // grouping elements and control base classes
18 
19 #include "C4Include.h"
20 #include "gui/C4Gui.h"
21 
22 #include "graphics/C4Draw.h"
23 #include "graphics/C4GraphicsResource.h"
24 #include "gui/C4MouseControl.h"
25 
26 namespace C4GUI
27 {
28 
29 // --------------------------------------------------
30 // Container
31 
Draw(C4TargetFacet & cgo)32 	void Container::Draw(C4TargetFacet &cgo)
33 	{
34 		// self visible?
35 		if (!IsVisible()) return;
36 		// then draw all visible child elements
37 		for (Element *pEl = pFirst; pEl; pEl = pEl->pNext)
38 			if (pEl->fVisible)
39 			{
40 				// skip viewport dialogs
41 				if (!pEl->IsExternalDrawDialog())
42 				{
43 					if (pEl->GetDialogWindow())
44 						pEl->GetDialogWindow()->RequestUpdate();
45 					else
46 						pEl->Draw(cgo);
47 				}
48 			}
49 	}
50 
Container()51 	Container::Container() : Element()
52 	{
53 		// zero fields
54 		pFirst = pLast = nullptr;
55 	}
56 
~Container()57 	Container::~Container()
58 	{
59 		// empty
60 		Clear();
61 	}
62 
Clear()63 	void Container::Clear()
64 	{
65 		ClearChildren();
66 	}
67 
ClearChildren()68 	void Container::ClearChildren()
69 	{
70 		// delete all items; dtor will update list
71 		while (pFirst)
72 		{
73 			if (pFirst->IsOwnPtrElement())
74 			{
75 				// unlink from list
76 				Element *pANext = pFirst->pNext;
77 				pFirst->pPrev = pFirst->pNext = nullptr;
78 				pFirst->pParent = nullptr;
79 				if ((pFirst = pANext))
80 					pFirst->pPrev = nullptr;
81 				else
82 					pLast = nullptr;
83 			}
84 			else
85 				delete pFirst;
86 		}
87 	}
88 
RemoveElement(Element * pChild)89 	void Container::RemoveElement(Element *pChild)
90 	{
91 		// safety
92 		if (!pChild) return;
93 		// inherited
94 		Element::RemoveElement(pChild);
95 		// must be from same container
96 		if (pChild->pParent != this) return;
97 		// unlink from list
98 		if (pChild->pPrev) pChild->pPrev->pNext = pChild->pNext; else pFirst = pChild->pNext;
99 		if (pChild->pNext) pChild->pNext->pPrev = pChild->pPrev; else pLast = pChild->pPrev;
100 		// unset parent; invalidates pPrev/pNext
101 		pChild->pParent = nullptr;
102 		// element has been removed
103 		AfterElementRemoval();
104 	}
105 
MakeLastElement(Element * pChild)106 	void Container::MakeLastElement(Element *pChild)
107 	{
108 		// must be from same container
109 		if (pChild->pParent != this) return;
110 		// unlink from list
111 		if (pChild->pPrev) pChild->pPrev->pNext = pChild->pNext; else pFirst = pChild->pNext;
112 		if (pChild->pNext) pChild->pNext->pPrev = pChild->pPrev; else pLast = pChild->pPrev;
113 		// readd to front of list
114 		if (pLast) pLast->pNext = pChild; else pFirst = pChild;
115 		pChild->pPrev = pLast; pChild->pNext = nullptr; pLast = pChild;
116 	}
117 
AddElement(Element * pChild)118 	void Container::AddElement(Element *pChild)
119 	{
120 		// safety
121 		if (!pChild) return;
122 		// remove from any previous container
123 		if (pChild->pParent) pChild->pParent->RemoveElement(pChild);
124 		// add to end of list
125 		if (pLast) pLast->pNext = pChild; else pFirst = pChild;
126 		pChild->pPrev = pLast; pChild->pNext = nullptr; pLast = pChild;
127 		pChild->pParent = this;
128 
129 		assert(pChild->pNext != pChild);
130 		assert(pChild->pPrev != pChild);
131 		assert(pChild->pParent != pChild);
132 	}
133 
ReaddElement(Element * pChild)134 	void Container::ReaddElement(Element *pChild)
135 	{
136 		// safety
137 		if (!pChild || pChild->pParent != this) return;
138 		// remove from any previous container
139 		if (pChild->pPrev) pChild->pPrev->pNext = pChild->pNext; else pFirst = pChild->pNext;
140 		if (pChild->pNext) pChild->pNext->pPrev = pChild->pPrev; else pLast = pChild->pPrev;
141 		// add to end of list
142 		if (pLast) pLast->pNext = pChild; else pFirst = pChild;
143 		pChild->pPrev = pLast; pChild->pNext = nullptr; pLast = pChild;
144 
145 		assert(pChild->pNext != pChild);
146 		assert(pChild->pPrev != pChild);
147 		assert(pChild->pParent != pChild);
148 	}
149 
InsertElement(Element * pChild,Element * pInsertBefore)150 	void Container::InsertElement(Element *pChild, Element *pInsertBefore)
151 	{
152 		// add?
153 		if (!pInsertBefore) { AddElement(pChild); return; }
154 		// safety
155 		if (!pChild || pInsertBefore->pParent != this) return;
156 		// remove from any previous container
157 		if (pChild->pParent) pChild->pParent->RemoveElement(pChild);
158 		// add before given element
159 		if ((pChild->pPrev = pInsertBefore->pPrev))
160 			pInsertBefore->pPrev->pNext = pChild;
161 		else
162 			pFirst = pChild;
163 		pChild->pNext = pInsertBefore; pInsertBefore->pPrev = pChild;
164 		pChild->pParent = this;
165 
166 		assert(pChild->pNext != pChild);
167 		assert(pChild->pPrev != pChild);
168 		assert(pChild->pParent != pChild);
169 	}
170 
GetNextNestedElement(Element * pPrevElement,bool fBackwards)171 	Element *Container::GetNextNestedElement(Element *pPrevElement, bool fBackwards)
172 	{
173 		if (fBackwards)
174 		{
175 			// this is last
176 			if (pPrevElement == this) return nullptr;
177 			// no previous given?
178 			if (!pPrevElement)
179 				// then use last nested for backwards search
180 				return GetFirstNestedElement(true);
181 			// get nested, previous element if present
182 			if (pPrevElement->pPrev) return pPrevElement->pPrev->GetFirstNestedElement(true);
183 			// if not, return parent (could be this)
184 			return pPrevElement->pParent;
185 		}
186 		else
187 		{
188 			// forward search: first element is this
189 			if (!pPrevElement) return this;
190 			// check next nested
191 			Element *pEl;
192 			if ((pEl = pPrevElement->GetFirstContained())) return pEl;
193 			// check next in list, going upwards until this container is reached
194 			while (pPrevElement && pPrevElement != this)
195 			{
196 				if ((pEl = pPrevElement->pNext)) return pEl;
197 				pPrevElement = pPrevElement->pParent;
198 			}
199 			// nothing found
200 		}
201 		return nullptr;
202 	}
203 
GetFirstNestedElement(bool fBackwards)204 	Element *Container::GetFirstNestedElement(bool fBackwards)
205 	{
206 		// get first/last in own list
207 		if (pFirst) return (fBackwards ? pLast : pFirst)->GetFirstNestedElement(fBackwards);
208 		// no own list: return this one
209 		return this;
210 	}
211 
OnHotkey(uint32_t cHotkey)212 	bool Container::OnHotkey(uint32_t cHotkey)
213 	{
214 		if (!IsVisible()) return false;
215 		// check all nested elements
216 		for (Element *pEl = pFirst; pEl; pEl=pEl->pNext)
217 			if (pEl->fVisible)
218 				if (pEl->OnHotkey(cHotkey)) return true;
219 		// no match found
220 		return false;
221 	}
222 
GetElementByIndex(int32_t i)223 	Element *Container::GetElementByIndex(int32_t i)
224 	{
225 		// get next until end of list or queried index is reached
226 		// if i is negative or equal or larger than childcount, the loop will never break and nullptr returned
227 		Element *pEl;
228 		for (pEl = pFirst; i-- && pEl; pEl=pEl->pNext) {}
229 		return pEl;
230 	}
231 
GetElementCount()232 	int32_t Container::GetElementCount()
233 	{
234 		int32_t cnt=0;
235 		for (Element *pEl = pFirst; pEl; pEl=pEl->pNext) ++cnt;
236 		return cnt;
237 	}
238 
IsParentOf(Element * pEl)239 	bool Container::IsParentOf(Element *pEl)
240 	{
241 		// return whether this is the parent container (directly or recursively) of the passed element
242 		for (Container *pC = pEl->GetParent(); pC; pC = pC->GetParent())
243 			if (pC == this) return true;
244 		return false;
245 	}
246 
SetVisibility(bool fToValue)247 	void Container::SetVisibility(bool fToValue)
248 	{
249 		// inherited
250 		Element::SetVisibility(fToValue);
251 		// remove focus from contained elements
252 		if (!fToValue)
253 		{
254 			Dialog *pDlg = GetDlg();
255 			if (pDlg)
256 			{
257 				Control *pFocus = pDlg->GetFocus();
258 				if (pFocus)
259 				{
260 					if (IsParentOf(pFocus))
261 					{
262 						pDlg->SetFocus(nullptr, false);
263 					}
264 				}
265 			}
266 		}
267 	}
268 
269 
270 // --------------------------------------------------
271 // Window
272 
MouseInput(CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)273 	void Window::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
274 	{
275 		// invisible?
276 		if (!IsVisible()) return;
277 		// inherited
278 		Container::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
279 		// get client pos
280 		C4Rect &rcClientRect = GetClientRect(), &rcBounds = GetBounds();
281 		iX -= rcClientRect.x - rcBounds.x; iY -= rcClientRect.y - rcBounds.y;
282 		// forward to topmost child element
283 		for (Element *pChild = pLast; pChild; pChild = pChild->GetPrev())
284 			if (pChild->fVisible && pChild->GetBounds().Contains(iX, iY))
285 			{
286 				// forward
287 				pChild->MouseInput(rMouse, iButton, iX - pChild->GetBounds().x, iY - pChild->GetBounds().y, dwKeyParam);
288 				// forward to one control only
289 				break;
290 			}
291 	}
292 
Draw(C4TargetFacet & cgo)293 	void Window::Draw(C4TargetFacet &cgo)
294 	{
295 		// invisible?
296 		if (!IsVisible()) return;
297 		// draw window itself
298 		DrawElement(cgo);
299 		// get target area
300 		C4Rect &rcClientRect = GetClientRect();
301 		C4Rect &rcClipArea = (IsComponentOutsideClientArea() ? GetBounds() : GetClientRect());
302 		// clip to window area
303 		int clx1, cly1, clx2, cly2;
304 		pDraw->GetPrimaryClipper(clx1, cly1, clx2, cly2);
305 		float nclx1 = cgo.TargetX+rcClipArea.x, ncly1 = cgo.TargetY+rcClipArea.y, nclx2 = cgo.TargetX+rcClipArea.x+rcClipArea.Wdt-1, ncly2 = cgo.TargetY+rcClipArea.y+rcClipArea.Hgt-1;
306 		pDraw->ApplyZoom(nclx1, ncly1);
307 		pDraw->ApplyZoom(nclx2, ncly2);
308 		pDraw->SubPrimaryClipper(nclx1, ncly1, nclx2, ncly2);
309 		// update target area
310 		cgo.TargetX += rcClientRect.x; cgo.TargetY += rcClientRect.y;
311 		// draw contents
312 		Container::Draw(cgo);
313 		// reset target area
314 		cgo.TargetX -= rcClientRect.x; cgo.TargetY -= rcClientRect.y;
315 		// reset clipper
316 		pDraw->SetPrimaryClipper(clx1, cly1, clx2, cly2);
317 	}
318 
Window()319 	Window::Window() : Container()
320 	{
321 		UpdateOwnPos();
322 	}
323 
UpdateOwnPos()324 	void Window::UpdateOwnPos()
325 	{
326 		Container::UpdateOwnPos();
327 		// set client rect
328 		int32_t iMarginL=GetMarginLeft(), iMarginT=GetMarginTop();
329 		rcClientRect.Set(rcBounds.x + iMarginL, rcBounds.y + iMarginT, std::max<int32_t>(rcBounds.Wdt - iMarginL - GetMarginRight(), 0), std::max<int32_t>(rcBounds.Hgt - iMarginT - GetMarginBottom(), 0));
330 	}
331 
332 
333 // --------------------------------------------------
334 // ScrollBar
335 
ScrollBar(C4Rect & rcBounds,ScrollWindow * pWin)336 	ScrollBar::ScrollBar(C4Rect &rcBounds, ScrollWindow *pWin) : fAutoHide(false), fHorizontal(false), iCBMaxRange(100), pScrollCallback(nullptr), pCustomGfx(nullptr)
337 	{
338 		// set bounds
339 		this->rcBounds = rcBounds;
340 		// set initial values
341 		pScrollWindow = pWin;
342 		fScrolling = false;
343 		iScrollThumbSize = C4GUI_ScrollThumbHgt; // vertical
344 		iScrollPos = 0;
345 		fTopDown = fBottomDown = false;
346 		// update scroll bar pos
347 		Update();
348 	}
349 
ScrollBar(C4Rect & rcBounds,bool fHorizontal,BaseParCallbackHandler<int32_t> * pCB,int32_t iCBMaxRange)350 	ScrollBar::ScrollBar(C4Rect &rcBounds, bool fHorizontal, BaseParCallbackHandler<int32_t> *pCB, int32_t iCBMaxRange) : fAutoHide(false), fHorizontal(fHorizontal), iCBMaxRange(iCBMaxRange), pScrollWindow(nullptr), pCustomGfx(nullptr)
351 	{
352 		// set bounds
353 		this->rcBounds = rcBounds;
354 		// set initial values
355 		if ((pScrollCallback = pCB)) pScrollCallback->Ref();
356 		fScrolling = true;
357 		iScrollThumbSize = fHorizontal ? C4GUI_ScrollThumbWdt : C4GUI_ScrollThumbHgt;
358 		iScrollPos = 0;
359 		fTopDown = fBottomDown = false;
360 	}
361 
~ScrollBar()362 	ScrollBar::~ScrollBar()
363 	{
364 		if (pScrollWindow) { pScrollWindow->pScrollBar = nullptr; }
365 		if (pScrollCallback) pScrollCallback->DeRef();
366 	}
367 
Update()368 	void ScrollBar::Update()
369 	{
370 		// check associated control
371 		if (pScrollWindow)
372 		{
373 			int32_t iVisHgt = pScrollWindow->GetBounds().Hgt;
374 			int32_t iClientHgt = pScrollWindow->GetClientRect().Hgt;
375 			if ((fScrolling = (iVisHgt < iClientHgt)))
376 			{
377 				// scrolling necessary
378 				// get vertical scroll pos
379 				int32_t iMaxWinScroll = iClientHgt - iVisHgt;
380 				int32_t iMaxBarScroll = GetBounds().Hgt - 2*C4GUI_ScrollArrowHgt - iScrollThumbSize;
381 				int32_t iWinScroll = pScrollWindow->iScrollY;
382 				// scroll thumb height is currently hardcoded
383 				// calc scroll pos
384 				iScrollPos = Clamp<int32_t>(iMaxBarScroll * iWinScroll / iMaxWinScroll, 0, iMaxBarScroll);
385 			}
386 		}
387 		else fScrolling = !!pScrollCallback;
388 		// reset buttons
389 		if (!fScrolling)
390 			fTopDown = fBottomDown = false;
391 		// set visibility by scroll status
392 		if (fAutoHide) SetVisibility(fScrolling);
393 	}
394 
OnPosChanged()395 	void ScrollBar::OnPosChanged()
396 	{
397 		int32_t iMaxBarScroll = GetMaxScroll();
398 		if (!iMaxBarScroll) iMaxBarScroll=1;
399 		// CB - passes scroll pos
400 		if (pScrollCallback) pScrollCallback->DoCall(Clamp<int32_t>(iScrollPos * (iCBMaxRange-1) / iMaxBarScroll, 0, (iCBMaxRange-1)));
401 		// safety
402 		if (!pScrollWindow || !fScrolling) return;
403 		// get scrolling values
404 		assert(!fHorizontal); // nyi
405 		int32_t iVisHgt = pScrollWindow->GetBounds().Hgt;
406 		int32_t iClientHgt = pScrollWindow->GetClientRect().Hgt;
407 		int32_t iMaxWinScroll = iClientHgt - iVisHgt;
408 		int32_t iWinScroll = pScrollWindow->iScrollY;
409 		// calc new window scrolling
410 		int32_t iNewWinScroll = Clamp<int32_t>(iMaxWinScroll * iScrollPos / iMaxBarScroll, 0, iMaxWinScroll);
411 		// apply it, if it is different
412 		if (iWinScroll != iNewWinScroll)
413 			pScrollWindow->SetScroll(iNewWinScroll);
414 	}
415 
MouseInput(CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)416 	void ScrollBar::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
417 	{
418 		// inherited
419 		Element::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
420 		// not if scrolling is disabled
421 		if (!fScrolling) return;
422 		// reset arrow states
423 		bool fPrevDown = fTopDown || fBottomDown;
424 		fTopDown = fBottomDown = false;
425 		// not if dragging
426 		if (rMouse.pDragElement) return;
427 		// left mouse button down?
428 		if (rMouse.IsLDown())
429 			// non-scroll-direction area check
430 			if (fHorizontal ? Inside<int32_t>(iY, 0, C4GUI_ScrollBarHgt) : Inside<int32_t>(iX, 0, C4GUI_ScrollBarWdt))
431 			{
432 				// scroll-direction area check: up/left arrow
433 				if (fHorizontal ? Inside<int32_t>(iX, 0, C4GUI_ScrollArrowWdt-1) : Inside<int32_t>(iY, 0, C4GUI_ScrollArrowHgt-1))
434 					fTopDown = true;
435 				// check down arrow
436 				else if (fHorizontal ? Inside<int32_t>(iX, GetBounds().Wdt-C4GUI_ScrollArrowWdt, GetBounds().Wdt-1)
437 				         : Inside<int32_t>(iY, GetBounds().Hgt-C4GUI_ScrollArrowHgt, GetBounds().Hgt-1))
438 					fBottomDown = true;
439 				else if (HasPin() && (fHorizontal ? Inside<int32_t>(iX, C4GUI_ScrollArrowWdt, GetBounds().Wdt-C4GUI_ScrollArrowWdt-1)
440 				                      : Inside<int32_t>(iY, C4GUI_ScrollArrowHgt, GetBounds().Hgt-C4GUI_ScrollArrowHgt-1)))
441 				{
442 					// move thumb here
443 					iScrollPos = GetScrollByPos(iX, iY);
444 					// reflect movement in associated window or do CB
445 					OnPosChanged();
446 					// start dragging
447 					rMouse.pDragElement = this;
448 					GUISound("UI::Select");
449 				}
450 			}
451 		// sound effekt when buttons are pressed
452 		if ((fTopDown || fBottomDown) != fPrevDown) GUISound("UI::Tick");
453 	}
454 
DoDragging(CMouse & rMouse,int32_t iX,int32_t iY,DWORD dwKeyParam)455 	void ScrollBar::DoDragging(CMouse &rMouse, int32_t iX, int32_t iY, DWORD dwKeyParam)
456 	{
457 		// move thumb
458 		iScrollPos = GetScrollByPos(iX, iY);
459 		// reflect movement in associated window
460 		OnPosChanged();
461 	}
462 
MouseLeave(CMouse & rMouse)463 	void ScrollBar::MouseLeave(CMouse &rMouse)
464 	{
465 		// inherited
466 		Element::MouseLeave(rMouse);
467 		// reset button states
468 		fTopDown = fBottomDown = false;
469 	}
470 
DrawElement(C4TargetFacet & cgo)471 	void ScrollBar::DrawElement(C4TargetFacet &cgo)
472 	{
473 		// do scrolling
474 		// not quite perfect; but there's no OnIdle, and it's be a bit of overkill starting a timer
475 		if (fTopDown && fScrolling && iScrollPos>0)
476 			{ --iScrollPos; OnPosChanged(); }
477 		if (fBottomDown && fScrolling)
478 		{
479 			if (iScrollPos < GetMaxScroll()) { ++iScrollPos; OnPosChanged(); }
480 		}
481 		// draw bar
482 		ScrollBarFacets &rUseGfx = pCustomGfx ? *pCustomGfx : ::GraphicsResource.sfctScroll;
483 		DynBarFacet bar = rUseGfx.barScroll;
484 		if (fTopDown) bar.fctBegin = rUseGfx.fctScrollDTop;
485 		if (fBottomDown) bar.fctEnd = rUseGfx.fctScrollDBottom;
486 		if (fHorizontal)
487 			DrawHBarByVGfx(cgo, bar);
488 		else
489 			DrawVBar(cgo, bar);
490 		// draw scroll pin
491 		if (fScrolling && HasPin())
492 		{
493 			if (fHorizontal)
494 				rUseGfx.fctScrollPin.Draw(cgo.Surface, cgo.TargetX+rcBounds.x+C4GUI_ScrollArrowWdt+iScrollPos, cgo.TargetY+rcBounds.y);
495 			else
496 				rUseGfx.fctScrollPin.Draw(cgo.Surface, cgo.TargetX+rcBounds.x, cgo.TargetY+rcBounds.y+C4GUI_ScrollArrowHgt+iScrollPos);
497 		}
498 	}
499 
500 
501 // --------------------------------------------------
502 // ScrollWindow
503 
ScrollWindow(Window * pParentWindow)504 	ScrollWindow::ScrollWindow(Window *pParentWindow)
505 			: Window(), pScrollBar(nullptr), iScrollY(0), iClientHeight(0), fHasBar(true), iFrozen(0)
506 	{
507 		// place within client rect
508 		C4Rect rtBounds = pParentWindow->GetClientRect();
509 		rtBounds.x = rtBounds.y = 0;
510 		rtBounds.Wdt -= C4GUI_ScrollBarWdt;
511 		SetBounds(rtBounds);
512 		// create scroll bar
513 		rtBounds.x += rtBounds.Wdt; rtBounds.Wdt = C4GUI_ScrollBarWdt;
514 		pScrollBar = new ScrollBar(rtBounds, this);
515 		// add self and scroll bar to window
516 		if (pParentWindow != this)
517 		{
518 			pParentWindow->AddElement(this);
519 			pParentWindow->AddElement(pScrollBar);
520 		}
521 	}
522 
Update()523 	void ScrollWindow::Update()
524 	{
525 		// not if window is being refilled
526 		if (iFrozen) return;
527 		// do not scroll outside range
528 		iScrollY = Clamp<int32_t>(iScrollY, 0, std::max<int32_t>(iClientHeight - GetBounds().Hgt, 0));
529 		// update client rect
530 		rcClientRect.x = 0;
531 		rcClientRect.y = -iScrollY;
532 		rcClientRect.Wdt = rcBounds.Wdt;
533 		rcClientRect.Hgt = iClientHeight;
534 		// update scroll bar
535 		if (pScrollBar) pScrollBar->Update();
536 	}
537 
SetScroll(int32_t iToScroll)538 	void ScrollWindow::SetScroll(int32_t iToScroll)
539 	{
540 		// set values
541 		rcClientRect.y = -(iScrollY = iToScroll);
542 	}
543 
ScrollToBottom()544 	void ScrollWindow::ScrollToBottom()
545 	{
546 		int32_t iVisHgt = GetBounds().Hgt;
547 		int32_t iClientHgt = GetClientRect().Hgt;
548 		int32_t iMaxScroll = iClientHgt - iVisHgt;
549 		if (iScrollY < iMaxScroll)
550 		{
551 			// scrolling possible: do it
552 			iScrollY = iMaxScroll;
553 			// update (self + bar)
554 			Update();
555 		}
556 	}
557 
ScrollPages(int iPageCount)558 	void ScrollWindow::ScrollPages(int iPageCount)
559 	{
560 		int32_t iVisHgt = GetBounds().Hgt;
561 		ScrollBy(iPageCount * iVisHgt);
562 	}
563 
ScrollBy(int iAmount)564 	void ScrollWindow::ScrollBy(int iAmount)
565 	{
566 		int32_t iVisHgt = GetBounds().Hgt;
567 		int32_t iClientHgt = GetClientRect().Hgt;
568 		int32_t iMaxScroll = iClientHgt - iVisHgt;
569 		int iNewScrollY = Clamp<int>(iScrollY + iAmount, 0, iMaxScroll);
570 		if (iScrollY != iNewScrollY)
571 		{
572 			// scrolling possible: do it
573 			iScrollY = iNewScrollY;
574 			// update (self + bar)
575 			Update();
576 		}
577 	}
578 
ScrollRangeInView(int32_t iY,int32_t iHgt)579 	void ScrollWindow::ScrollRangeInView(int32_t iY, int32_t iHgt)
580 	{
581 		// safety bounds
582 		if (iY<0) iY=0;
583 		int32_t iClientHgt = GetClientRect().Hgt;
584 		if (iY+iHgt > iClientHgt) { ScrollToBottom(); return; }
585 		// check top
586 		if (iScrollY > iY)
587 		{
588 			iScrollY = iY;
589 			Update(); // update (self+bar)
590 		}
591 		else
592 		{
593 			// check bottom
594 			int32_t iVisHgt = GetBounds().Hgt;
595 			// if no height is given, scroll given Y-pos to top
596 			if (!iHgt) iHgt = iVisHgt;
597 			if (iScrollY + iVisHgt < iY + iHgt)
598 			{
599 				iScrollY = iY + iHgt - iVisHgt;
600 				Update(); // update (self+bar)
601 			}
602 		}
603 	}
604 
IsRangeInView(int32_t iY,int32_t iHgt)605 	bool ScrollWindow::IsRangeInView(int32_t iY, int32_t iHgt)
606 	{
607 		// returns whether scrolling range is in view
608 		// check top
609 		if (iScrollY > iY) return false;
610 		// check height
611 		return iScrollY + GetBounds().Hgt >= iY+iHgt;
612 	}
613 
UpdateOwnPos()614 	void ScrollWindow::UpdateOwnPos()
615 	{
616 		if (!GetParent()) { Update(); return; }
617 		// place within client rect
618 		C4Rect rtBounds = GetParent()->GetContainedClientRect();
619 		rtBounds.x = rtBounds.y = 0;
620 		if (fHasBar) rtBounds.Wdt -= C4GUI_ScrollBarWdt;
621 		if (GetBounds() != rtBounds)
622 		{
623 			SetBounds(rtBounds);
624 			// scroll bar
625 			if (fHasBar)
626 			{
627 				rtBounds.x += rtBounds.Wdt; rtBounds.Wdt = C4GUI_ScrollBarWdt;
628 				pScrollBar->SetBounds(rtBounds);
629 			}
630 		}
631 		// standard updates
632 		Update();
633 	}
634 
SetScrollBarEnabled(bool fToVal,bool noAutomaticPositioning)635 	void ScrollWindow::SetScrollBarEnabled(bool fToVal, bool noAutomaticPositioning)
636 	{
637 		if (fHasBar == fToVal) return;
638 		pScrollBar->SetVisibility(fHasBar = fToVal);
639 		// in some cases the windows will already care for the correct positioning themselves (see C4ScriptGuiWindow)
640 		if (!noAutomaticPositioning)
641 			UpdateOwnPos();
642 	}
643 
MouseInput(CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)644 	void ScrollWindow::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
645 	{
646 		// process wheel: Scroll
647 		if (iButton == C4MC_Button_Wheel)
648 		{
649 			short iDelta = (short)(dwKeyParam >> 16);
650 			ScrollBy(-iDelta);
651 			return;
652 		}
653 		// other mouse input: inherited (forward to children)
654 		Window::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
655 	}
656 
657 
658 // --------------------------------------------------
659 // GroupBox
660 
GetTitleFont() const661 	CStdFont *GroupBox::GetTitleFont() const
662 	{
663 		// get font; fallback to GUI caption font
664 		return pFont ? pFont : &(::GraphicsResource.CaptionFont);
665 	}
666 
DrawElement(C4TargetFacet & cgo)667 	void GroupBox::DrawElement(C4TargetFacet &cgo)
668 	{
669 		// draw background
670 		if (dwBackClr != 0xffffffff)
671 		{
672 			pDraw->DrawBoxDw(cgo.Surface, cgo.TargetX+rcBounds.x, cgo.TargetY+rcBounds.y, cgo.TargetX+rcBounds.x+rcBounds.Wdt-1, cgo.TargetY+rcBounds.y+rcBounds.Hgt-1, dwBackClr);
673 		}
674 		// draw title label
675 		int32_t iBorderYOff = 0;
676 		int32_t iTitleGapX = 0;
677 		int32_t iTitleGapWdt = 0;
678 		if (HasTitle())
679 		{
680 			CStdFont *pTitleFont = GetTitleFont();
681 			iBorderYOff = pTitleFont->GetLineHeight()/2;
682 			pTitleFont->GetTextExtent(sTitle.getData(), iTitleGapWdt, iTitleGapX, true);
683 			iTitleGapX = 7; iTitleGapWdt += 4;
684 			pDraw->TextOut(sTitle.getData(), *pTitleFont, 1.0f, cgo.Surface, cgo.TargetX+rcBounds.x+iTitleGapX+2, cgo.TargetY+rcBounds.y, dwTitleClr);
685 		}
686 		// draw frame
687 		if (dwFrameClr)
688 		{
689 			int32_t x1=cgo.TargetX+rcBounds.x,y1=cgo.TargetY+rcBounds.y+iBorderYOff,x2=x1+rcBounds.Wdt,y2=y1+rcBounds.Hgt-iBorderYOff;
690 			if (iTitleGapWdt)
691 			{
692 				for (int i=0; i<2; ++i)
693 				{
694 					pDraw->DrawLineDw(cgo.Surface, (float) x1+i, (float)y1, (float)(x1+i), (float)(y2-1), dwFrameClr); // left
695 					pDraw->DrawLineDw(cgo.Surface, (float) (x1+2), (float)(y1+i), (float)(x1+iTitleGapX), (float)(y1+i), dwFrameClr); // top - left side
696 					pDraw->DrawLineDw(cgo.Surface, (float) (x1+iTitleGapX+iTitleGapWdt), (float)(y1+i), (float)(x2-3), (float)(y1+i), dwFrameClr); // top - right side
697 					pDraw->DrawLineDw(cgo.Surface, (float) (x2-1-i), (float)y1, (float)(x2-1-i), (float)(y2-1), dwFrameClr); // right
698 					pDraw->DrawLineDw(cgo.Surface, (float) (x1+2), (float)(y2-1-i), (float)(x2-3), (float)(y2-1-i), dwFrameClr); // bottom
699 				}
700 			}
701 			else
702 			{
703 				pDraw->DrawFrameDw(cgo.Surface, x1, y1, x2, (y2-1), dwFrameClr);
704 				pDraw->DrawFrameDw(cgo.Surface, (x1+1), (y1+1), (x2-1), (y2-2), dwFrameClr);
705 			}
706 		}
707 		else
708 			// default frame color
709 			// 2do: Make this work with titled group boxes
710 			Draw3DFrame(cgo);
711 	}
712 
713 
714 // --------------------------------------------------
715 // Control
716 
Control(const C4Rect & rtBounds)717 	Control::Control(const C4Rect &rtBounds) : Window()
718 	{
719 		// set bounds
720 		SetBounds(rtBounds);
721 		// context menu key binding
722 		pKeyContext = new C4KeyBinding(C4KeyCodeEx(K_MENU), "GUIContext", KEYSCOPE_Gui,
723 		                               new ControlKeyCB<Control>(*this, &Control::KeyContext), C4CustomKey::PRIO_Ctrl);
724 	}
725 
~Control()726 	Control::~Control()
727 	{
728 		delete pKeyContext;
729 	}
730 
MouseInput(CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)731 	void Control::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
732 	{
733 		if (!IsVisible()) return;
734 		// left down on click=focus-components?
735 		if (IsFocusOnClick() && IsFocusElement()) if (iButton == C4MC_Button_LeftDown && !HasFocus())
736 			{
737 				// then set focus
738 				Dialog *pParentDlg = GetDlg();
739 				if (pParentDlg)
740 				{
741 					// but do not set focus to this if a child control has it already
742 					Control *pActiveCtrl = pParentDlg->GetFocus();
743 					if (!pActiveCtrl || !IsParentOf(pActiveCtrl))
744 						pParentDlg->SetFocus(this, true);
745 				}
746 			}
747 		// inherited - processing child elements
748 		Window::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
749 	}
750 
HasDrawFocus()751 	bool Control::HasDrawFocus()
752 	{
753 		// has focus at all?
754 		if (!HasFocus()) return false;
755 		// is screen ready and not in context?
756 		if (GetScreen() && GetScreen()->HasContext()) return false;
757 		// get dlg
758 		Dialog *pDlg=GetDlg();
759 		// dlg-less control has focus, OK (shouldn't happen)
760 		if (!pDlg) return true;
761 		// check if dlg is active
762 		return pDlg->IsActive(true);
763 	}
764 
DisableFocus()765 	void Control::DisableFocus()
766 	{
767 		// has it any focus at all?
768 		if (!HasFocus()) return;
769 		// then de-focus it
770 		Dialog *pDlg=GetDlg();
771 		if (!pDlg) return;
772 		pDlg->AdvanceFocus(true);
773 	}
774 
775 } // end of namespace
776 
777