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