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