| 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 | // eye candy |
| 20 | |
| 21 | #include <C4Include.h> |
| 22 | #include <C4Gui.h> |
| 23 | #include <C4FullScreen.h> |
| 24 | #include <C4LoaderScreen.h> |
| 25 | #include "C4GuiResource.h" |
| 26 | #include "C4OpenURL.h" |
| 27 | #include <C4Application.h> |
| 28 | |
| 29 | namespace C4GUI |
| 30 | { |
| 31 | |
| 32 | // Label |
| 33 | |
| 34 | void Label::DrawElement(C4FacetEx &cgo) |
| 35 | { |
| 36 | // print out |
| 37 | lpDDraw->TextOut(szText: sText.getData(), rFont&: *pFont, fZoom: 1.0f, sfcDest: cgo.Surface, iTx: x0 + cgo.TargetX, iTy: rcBounds.y + cgo.TargetY, dwFCol: dwFgClr, byForm: iAlign, fDoMarkup: fMarkup); |
| 38 | if (sHyperlink.getData()) |
| 39 | { |
| 40 | int32_t iLinkWdt = 10, iLinkHgt = 10; |
| 41 | pFont->GetTextExtent(szText: sText.getData(), rsx&: iLinkWdt, rsy&: iLinkHgt, fCheckMarkup: fMarkup); |
| 42 | lpDDraw->DrawLineDw(sfcTarget: cgo.Surface, |
| 43 | x1: static_cast<float>(rcBounds.x + cgo.TargetX), |
| 44 | y1: static_cast<float>(rcBounds.y + iLinkHgt - 2 + cgo.TargetY), |
| 45 | x2: static_cast<float>(rcBounds.x + iLinkWdt + cgo.TargetX), |
| 46 | y2: static_cast<float>(rcBounds.y + iLinkHgt - 2 + cgo.TargetY), |
| 47 | C4GUI_HyperlinkFontClr & 0xffffff |
| 48 | ); |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | Label::Label(std::string_view lblText, int32_t iX0, int32_t iTop, int32_t iAlign, uint32_t dwFClr, CStdFont *pFont, bool fMakeReadableOnBlack, bool fMarkup) |
| 53 | : Element(), dwFgClr(dwFClr), x0(iX0), iAlign(iAlign), pFont(pFont), cHotkey(0), pClickFocusControl(nullptr), fAutosize(true), fMarkup(fMarkup) |
| 54 | { |
| 55 | // make color readable |
| 56 | if (fMakeReadableOnBlack) MakeColorReadableOnBlack(rdwClr&: dwFgClr); |
| 57 | // default font |
| 58 | if (!this->pFont) this->pFont = &GetRes()->TextFont; |
| 59 | // set top |
| 60 | rcBounds.y = iTop; |
| 61 | // update text |
| 62 | SetText(toText: lblText); |
| 63 | } |
| 64 | |
| 65 | Label::Label(std::string_view lblText, const C4Rect &rcBounds, int32_t iAlign, uint32_t dwFClr, CStdFont *pFont, bool fMakeReadableOnBlack, bool fAutosize, bool fMarkup) |
| 66 | : Element(), dwFgClr(dwFClr), iAlign(iAlign), pFont(pFont), cHotkey(0), pClickFocusControl(nullptr), fAutosize(fAutosize), fMarkup(fMarkup) |
| 67 | { |
| 68 | // make color readable |
| 69 | if (fMakeReadableOnBlack) MakeColorReadableOnBlack(rdwClr&: dwFgClr); |
| 70 | this->rcBounds = rcBounds; |
| 71 | // default font |
| 72 | if (!this->pFont) this->pFont = &GetRes()->TextFont; |
| 73 | // set x0 |
| 74 | UpdateOwnPos(); |
| 75 | // update text |
| 76 | SetText(toText: lblText); |
| 77 | } |
| 78 | |
| 79 | void Label::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, uint32_t dwKeyParam) |
| 80 | { |
| 81 | // left-click changes focus |
| 82 | if (iButton == C4MC_Button_LeftDown) |
| 83 | { |
| 84 | if (pClickFocusControl) GetDlg()->SetFocus(pCtrl: pClickFocusControl, fByMouse: true); |
| 85 | // left click opens URL |
| 86 | if (sHyperlink.getData()) OpenURL(szURL: sHyperlink.getData()); |
| 87 | } |
| 88 | // inherited |
| 89 | Element::MouseInput(rMouse, iButton, iX, iY, dwKeyParam); |
| 90 | } |
| 91 | |
| 92 | void Label::SetText(const char *szText, bool fAllowHotkey) |
| 93 | { |
| 94 | return SetText(toText: szText ? std::string_view{szText} : std::string_view{}, fAllowHotkey); |
| 95 | } |
| 96 | |
| 97 | void Label::SetText(std::string_view toText, bool fAllowHotkey) |
| 98 | { |
| 99 | // set new text |
| 100 | if (!toText.empty()) |
| 101 | { |
| 102 | sText.Copy(pnData: toText.data(), iChars: toText.size()); |
| 103 | // expand hotkey markup |
| 104 | if (fAllowHotkey && fMarkup) ExpandHotkeyMarkup(sText, rcHotkey&: cHotkey); |
| 105 | } |
| 106 | else |
| 107 | { |
| 108 | sText = "" ; |
| 109 | cHotkey = 0; |
| 110 | } |
| 111 | // update according to text only if autosize label (not wooden) |
| 112 | if (!fAutosize) return; |
| 113 | // get text extents |
| 114 | pFont->GetTextExtent(szText: sText.getData(), rsx&: rcBounds.Wdt, rsy&: rcBounds.Hgt, fCheckMarkup: fMarkup); |
| 115 | // update pos |
| 116 | SetX0(x0); |
| 117 | } |
| 118 | |
| 119 | void Label::UpdateOwnPos() |
| 120 | { |
| 121 | // update text drawing pos |
| 122 | switch (iAlign) |
| 123 | { |
| 124 | case ALeft: x0 = rcBounds.x + GetLeftIndent(); break; |
| 125 | case ACenter: x0 = rcBounds.x + rcBounds.Wdt / 2; break; |
| 126 | case ARight: x0 = rcBounds.x + rcBounds.Wdt; break; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | bool Label::OnHotkey(char cHotkey) |
| 131 | { |
| 132 | // if hotkey matches and focus control is assigned, set focus |
| 133 | if (this->cHotkey == cHotkey && pClickFocusControl) |
| 134 | { |
| 135 | GetDlg()->SetFocus(pCtrl: pClickFocusControl, fByMouse: false); |
| 136 | return true; |
| 137 | } |
| 138 | else return false; |
| 139 | } |
| 140 | |
| 141 | void Label::SetX0(int32_t iToX0) |
| 142 | { |
| 143 | x0 = iToX0; |
| 144 | // update x-startpos |
| 145 | switch (iAlign) |
| 146 | { |
| 147 | case ALeft: rcBounds.x = x0; break; |
| 148 | case ACenter: rcBounds.x = x0 - rcBounds.Wdt / 2; break; |
| 149 | case ARight: rcBounds.x = x0 - rcBounds.Wdt; break; |
| 150 | } |
| 151 | // size might have changed |
| 152 | UpdateSize(); |
| 153 | } |
| 154 | |
| 155 | void Label::SetHyperlink(const char *szURL) |
| 156 | { |
| 157 | if (szURL && *szURL) |
| 158 | { |
| 159 | sHyperlink.Copy(pnData: szURL); |
| 160 | SetColor(C4GUI_HyperlinkFontClr, fMakeReadableOnBlack: false); |
| 161 | } |
| 162 | else |
| 163 | sHyperlink.Clear(); |
| 164 | } |
| 165 | |
| 166 | // WoodenLabel |
| 167 | |
| 168 | void WoodenLabel::DrawElement(C4FacetEx &cgo) |
| 169 | { |
| 170 | // draw wood |
| 171 | DrawBar(cgo, rFacets&: GetRes()->barCaption); |
| 172 | // draw symbol |
| 173 | if (fctIcon.Surface) |
| 174 | { |
| 175 | C4Facet cgoSymbol(cgo.Surface, cgo.TargetX + rcBounds.x + 1, cgo.TargetY + rcBounds.y + 1, rcBounds.Hgt - 2, rcBounds.Hgt - 2); |
| 176 | fctIcon.Draw(cgo&: cgoSymbol); |
| 177 | } |
| 178 | // calculations for automatic scrolling |
| 179 | int32_t iXOff = 0; |
| 180 | if (iAlign == ALeft) iXOff += 5; |
| 181 | if (tAutoScrollDelay) |
| 182 | { |
| 183 | time_t tNow = timeGetTime(); |
| 184 | if (!tLastChangeTime) |
| 185 | tLastChangeTime = tNow; |
| 186 | else if (tNow - tLastChangeTime >= tAutoScrollDelay) |
| 187 | { |
| 188 | if (!iScrollDir) iScrollDir = 1; |
| 189 | int32_t iMaxScroll = std::max<int32_t>(a: pFont->GetTextWidth(szText: sText.getData(), fCheckMarkup: true) + (x0 - rcBounds.x) + iXOff + GetRightIndent() - rcBounds.Wdt, b: 0); |
| 190 | if (iMaxScroll) |
| 191 | { |
| 192 | iScrollPos += iScrollDir; |
| 193 | if (iScrollPos >= iMaxScroll || iScrollPos < 0) |
| 194 | { |
| 195 | iScrollDir = -iScrollDir; |
| 196 | iScrollPos += iScrollDir; |
| 197 | tLastChangeTime = tNow; |
| 198 | } |
| 199 | } |
| 200 | } |
| 201 | iXOff -= iScrollPos; |
| 202 | } |
| 203 | // print out text; clipped |
| 204 | int ClipX1, ClipY1, ClipX2, ClipY2; |
| 205 | lpDDraw->GetPrimaryClipper(rX1&: ClipX1, rY1&: ClipY1, rX2&: ClipX2, rY2&: ClipY2); |
| 206 | lpDDraw->SetPrimaryClipper(iX1: rcBounds.x + GetLeftIndent() + cgo.TargetX, iY1: rcBounds.y + cgo.TargetY, iX2: rcBounds.x + rcBounds.Wdt - GetRightIndent() + cgo.TargetX, iY2: rcBounds.y + rcBounds.Hgt + cgo.TargetY); |
| 207 | lpDDraw->TextOut(szText: sText.getData(), rFont&: *pFont, fZoom: 1.0f, sfcDest: cgo.Surface, iTx: x0 + cgo.TargetX + iXOff, iTy: rcBounds.y + cgo.TargetY + (rcBounds.Hgt - pFont->GetLineHeight()) / 2 - 1, dwFCol: dwFgClr, byForm: iAlign); |
| 208 | lpDDraw->SetPrimaryClipper(iX1: ClipX1, iY1: ClipY1, iX2: ClipX2, iY2: ClipY2); |
| 209 | } |
| 210 | |
| 211 | int32_t WoodenLabel::GetDefaultHeight(CStdFont *pUseFont) |
| 212 | { |
| 213 | if (!pUseFont) pUseFont = &(GetRes()->TextFont); |
| 214 | return std::max<int32_t>(a: pUseFont->GetLineHeight(), C4GUI_MinWoodBarHgt); |
| 215 | } |
| 216 | |
| 217 | void WoodenLabel::SetIcon(const C4Facet &rfctIcon) |
| 218 | { |
| 219 | // set icon |
| 220 | fctIcon = rfctIcon; |
| 221 | // realign text to left for set icons |
| 222 | if (fctIcon.Surface) |
| 223 | iAlign = ALeft; |
| 224 | else |
| 225 | iAlign = ACenter; |
| 226 | UpdateOwnPos(); |
| 227 | } |
| 228 | |
| 229 | // MultilineLabel |
| 230 | |
| 231 | MultilineLabel::MultilineLabel(const C4Rect &rcBounds, int32_t iMaxLines, int32_t iMaxBuf, const char *szIndentChars, bool fAutoGrow, bool fMarkup) |
| 232 | : Element(), Lines(iMaxBuf, iMaxLines, rcBounds.Wdt, szIndentChars, fAutoGrow, fMarkup), fMarkup(fMarkup) |
| 233 | { |
| 234 | // set bounds |
| 235 | this->rcBounds = rcBounds; |
| 236 | // update height (min height) |
| 237 | UpdateOwnPos(); |
| 238 | } |
| 239 | |
| 240 | void MultilineLabel::DrawElement(C4FacetEx &cgo) |
| 241 | { |
| 242 | // get clipping |
| 243 | int iClipX, iClipY, iClipX2, iClipY2; |
| 244 | lpDDraw->GetPrimaryClipper(rX1&: iClipX, rY1&: iClipY, rX2&: iClipX2, rY2&: iClipY2); |
| 245 | // draw all lines |
| 246 | int32_t iIndex = 0; const char *szLine; |
| 247 | int32_t iY = rcBounds.y + cgo.TargetY; |
| 248 | CStdFont *pLineFont; uint32_t dwLineClr; bool fNewParagraph; |
| 249 | while (szLine = Lines.GetLine(iLineIndex: iIndex, ppFont: &pLineFont, pdwClr: &dwLineClr, pNewParagraph: &fNewParagraph)) |
| 250 | { |
| 251 | int32_t iFontLineHeight = pLineFont->GetLineHeight(); |
| 252 | // indents between paragraphs |
| 253 | if (fNewParagraph && iIndex) iY += iFontLineHeight / 3; |
| 254 | // clip |
| 255 | if (iY > iClipY2) break; |
| 256 | if (iY >= iClipY - iFontLineHeight) |
| 257 | { |
| 258 | // draw line |
| 259 | lpDDraw->TextOut(szText: szLine, rFont&: *pLineFont, fZoom: 1.0f, sfcDest: cgo.Surface, iTx: rcBounds.x + cgo.TargetX, iTy: iY, dwFCol: dwLineClr, byForm: ALeft, fDoMarkup: fMarkup); |
| 260 | } |
| 261 | // advance line |
| 262 | iY += iFontLineHeight; |
| 263 | ++iIndex; |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | void MultilineLabel::UpdateSize() |
| 268 | { |
| 269 | // forward change to line buffer |
| 270 | Lines.SetLBWidth(rcBounds.Wdt); |
| 271 | UpdateHeight(); |
| 272 | } |
| 273 | |
| 274 | void MultilineLabel::UpdateHeight() |
| 275 | { |
| 276 | // size by line count |
| 277 | int32_t iIndex = 0; const char *szLine; int32_t iHgt = 0; |
| 278 | CStdFont *pLineFont; bool fNewPar; |
| 279 | while (szLine = Lines.GetLine(iLineIndex: iIndex, ppFont: &pLineFont, pdwClr: nullptr, pNewParagraph: &fNewPar)) |
| 280 | { |
| 281 | int32_t iFontLineHeight = pLineFont->GetLineHeight(); |
| 282 | // indents between separate messages |
| 283 | if (fNewPar && iIndex) iHgt += iFontLineHeight / 3; |
| 284 | // text line height |
| 285 | iHgt += iFontLineHeight; |
| 286 | ++iIndex; |
| 287 | } |
| 288 | rcBounds.Hgt = std::max<int32_t>(a: iHgt, b: 5); |
| 289 | // update parent container |
| 290 | Element::UpdateSize(); |
| 291 | } |
| 292 | |
| 293 | void MultilineLabel::AddLine(const char *szLine, CStdFont *pFont, uint32_t dwClr, bool fDoUpdate, bool fMakeReadableOnBlack, CStdFont *pCaptionFont) |
| 294 | { |
| 295 | // make color readable |
| 296 | if (fMakeReadableOnBlack) MakeColorReadableOnBlack(rdwClr&: dwClr); |
| 297 | // forward to line buffer |
| 298 | if (szLine) Lines.AppendLines(szLine, pFont, dwClr, pFirstLineFont: pCaptionFont); |
| 299 | // adjust height |
| 300 | if (fDoUpdate) UpdateSize(); |
| 301 | } |
| 302 | |
| 303 | void MultilineLabel::Clear(bool fDoUpdate) |
| 304 | { |
| 305 | // forward to line buffer |
| 306 | Lines.Clear(); |
| 307 | // adjust height |
| 308 | if (fDoUpdate) UpdateSize(); |
| 309 | } |
| 310 | |
| 311 | // HorizontalLine |
| 312 | |
| 313 | void HorizontalLine::DrawElement(C4FacetEx &cgo) |
| 314 | { |
| 315 | // draw horizontal line |
| 316 | int32_t iX1 = rcBounds.x + cgo.TargetX, iX2 = iX1 + rcBounds.Wdt, |
| 317 | iY = rcBounds.y + cgo.TargetY; |
| 318 | lpDDraw->DrawLineDw(sfcTarget: cgo.Surface, x1: static_cast<float>(iX1 + 1), y1: static_cast<float>(iY + 1), x2: static_cast<float>(iX2 - 1), y2: static_cast<float>(iY + 1), dwClr: dwShadowClr); |
| 319 | lpDDraw->DrawLineDw(sfcTarget: cgo.Surface, x1: static_cast<float>(iX1), y1: static_cast<float>(iY), x2: static_cast<float>(iX2 - 2), y2: static_cast<float>(iY), dwClr); |
| 320 | } |
| 321 | |
| 322 | // ProgressBar |
| 323 | |
| 324 | void ProgressBar::DrawElement(C4FacetEx &cgo) |
| 325 | { |
| 326 | // do not draw in negative progress |
| 327 | if (iProgress < 0) return; |
| 328 | CStdFont &rFont = GetRes()->TextFont; |
| 329 | // draw border |
| 330 | Draw3DFrame(cgo); |
| 331 | // calc progress width |
| 332 | int32_t iProgressWdt = (rcBounds.Wdt - 4) * iProgress / iMax; |
| 333 | // draw progress |
| 334 | GetRes()->fctProgressBar.DrawX(sfcTarget: cgo.Surface, iX: cgo.TargetX + rcBounds.x + 2, iY: cgo.TargetY + rcBounds.y + 2, iWdt: iProgressWdt, iHgt: rcBounds.Hgt - 2); |
| 335 | // print out progress text |
| 336 | lpDDraw->TextOut(szText: (std::to_string(val: 100 * iProgress / iMax) + "%" ).c_str(), rFont, fZoom: 1.0f, sfcDest: cgo.Surface, iTx: cgo.TargetX + rcBounds.GetMiddleX(), iTy: rcBounds.y + cgo.TargetY + (rcBounds.Hgt - rFont.GetLineHeight()) / 2 - 1, C4GUI_ProgressBarFontClr, byForm: ACenter); |
| 337 | } |
| 338 | |
| 339 | // Picture |
| 340 | |
| 341 | Picture::Picture(const C4Rect &rcBounds, bool fAspect) : fCustomDrawClr(false), fAnimate(false) |
| 342 | { |
| 343 | // set values |
| 344 | this->fAspect = fAspect; |
| 345 | this->rcBounds = rcBounds; |
| 346 | // no facet yet |
| 347 | } |
| 348 | |
| 349 | void Picture::DrawElement(C4FacetEx &cgo) |
| 350 | { |
| 351 | // animation? |
| 352 | C4Facet *pDrawFacet, DrawFacet; |
| 353 | if (fAnimate) |
| 354 | { |
| 355 | if (++iPhaseTime > iDelay) |
| 356 | { |
| 357 | int32_t iPhasesX = 1, iPhasesY = 1; |
| 358 | Facet.GetPhaseNum(rX&: iPhasesX, rY&: iPhasesY); |
| 359 | if (++iAnimationPhase >= iPhasesX) iAnimationPhase = 0; |
| 360 | iPhaseTime = 0; |
| 361 | } |
| 362 | DrawFacet = Facet.GetPhase(iPhaseX: iAnimationPhase); |
| 363 | pDrawFacet = &DrawFacet; |
| 364 | } |
| 365 | else |
| 366 | pDrawFacet = &Facet; |
| 367 | // draw the facet |
| 368 | C4Facet cgo2 = cgo; |
| 369 | cgo2.X = rcBounds.x + cgo.TargetX; |
| 370 | cgo2.Y = rcBounds.y + cgo.TargetY; |
| 371 | cgo2.Wdt = rcBounds.Wdt; |
| 372 | cgo2.Hgt = rcBounds.Hgt; |
| 373 | if (fCustomDrawClr) |
| 374 | { |
| 375 | pDrawFacet->DrawClr(cgo&: cgo2, fAspect, dwClr: dwDrawClr); |
| 376 | } |
| 377 | else |
| 378 | pDrawFacet->Draw(cgo&: cgo2, fAspect); |
| 379 | } |
| 380 | |
| 381 | bool Picture::EnsureOwnSurface() |
| 382 | { |
| 383 | // no surface? |
| 384 | if (!Facet.Surface) return false; |
| 385 | // equals face already? |
| 386 | if (Facet.Surface == &Facet.GetFace()) return true; |
| 387 | // then create as a copy |
| 388 | C4Facet cgo = Facet; |
| 389 | if (!Facet.Create(iWdt: cgo.Wdt, iHgt: cgo.Hgt)) return false; |
| 390 | cgo.Draw(cgo&: Facet); |
| 391 | return true; |
| 392 | } |
| 393 | |
| 394 | void Picture::SetAnimated(bool fEnabled, int iDelay) |
| 395 | { |
| 396 | if (fAnimate = fEnabled) |
| 397 | { |
| 398 | // starts cycling through all phases of the specified facet |
| 399 | iAnimationPhase = iPhaseTime = 0; |
| 400 | this->iDelay = iDelay; |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | // OverlayPicture |
| 405 | |
| 406 | OverlayPicture::OverlayPicture(const C4Rect &rcBounds, bool fAspect, const C4Facet &rOverlayImage, int iBorderSize) |
| 407 | : Picture(rcBounds, fAspect), iBorderSize(iBorderSize), OverlayImage(rOverlayImage) {} |
| 408 | |
| 409 | void OverlayPicture::DrawElement(C4FacetEx &cgo) |
| 410 | { |
| 411 | // draw inner image |
| 412 | C4Facet cgo2 = cgo; |
| 413 | cgo2.X = rcBounds.x + cgo.TargetX + iBorderSize * rcBounds.Wdt / std::max<int>(a: OverlayImage.Wdt, b: 1); |
| 414 | cgo2.Y = rcBounds.y + cgo.TargetY + iBorderSize * rcBounds.Hgt / std::max<int>(a: OverlayImage.Hgt, b: 1); |
| 415 | cgo2.Wdt = rcBounds.Wdt - 2 * iBorderSize * rcBounds.Wdt / std::max<int>(a: OverlayImage.Wdt, b: 1); |
| 416 | cgo2.Hgt = rcBounds.Hgt - 2 * iBorderSize * rcBounds.Hgt / std::max<int>(a: OverlayImage.Hgt, b: 1); |
| 417 | Facet.Draw(cgo&: cgo2, fAspect); |
| 418 | // draw outer image |
| 419 | cgo2.X = rcBounds.x + cgo.TargetX; |
| 420 | cgo2.Y = rcBounds.y + cgo.TargetY; |
| 421 | cgo2.Wdt = rcBounds.Wdt; |
| 422 | cgo2.Hgt = rcBounds.Hgt; |
| 423 | OverlayImage.Draw(cgo&: cgo2, fAspect); |
| 424 | } |
| 425 | |
| 426 | // Icon |
| 427 | |
| 428 | Icon::Icon(const C4Rect &rcBounds, Icons icoIconIndex) |
| 429 | : Picture(rcBounds, true) |
| 430 | { |
| 431 | // set icon facet |
| 432 | SetIcon(icoIconIndex); |
| 433 | } |
| 434 | |
| 435 | void Icon::SetIcon(Icons icoNewIconIndex) |
| 436 | { |
| 437 | // load icon |
| 438 | SetFacet(GetIconFacet(icoIconIndex: icoNewIconIndex)); |
| 439 | } |
| 440 | |
| 441 | C4FacetEx Icon::GetIconFacet(Icons icoIconIndex) |
| 442 | { |
| 443 | if (icoIconIndex == Ico_None) return C4FacetEx(); |
| 444 | C4FacetEx &rFacet = (icoIconIndex & Ico_Extended) ? GetRes()->fctIconsEx : GetRes()->fctIcons; |
| 445 | icoIconIndex = Icons(icoIconIndex & (Ico_Extended - 1)); |
| 446 | int32_t iXMax, iYMax; |
| 447 | rFacet.GetPhaseNum(rX&: iXMax, rY&: iYMax); |
| 448 | if (!iXMax) iXMax = 6; |
| 449 | return rFacet.GetPhase(iPhaseX: icoIconIndex % iXMax, iPhaseY: icoIconIndex / iXMax); |
| 450 | } |
| 451 | |
| 452 | // TextWindow |
| 453 | |
| 454 | TextWindow::TextWindow(const C4Rect &rtBounds, size_t iPicWdt, size_t iPicHgt, size_t iPicPadding, size_t iMaxLines, size_t iMaxTextLen, const char *szIndentChars, bool fAutoGrow, const C4Facet *pOverlayPic, int iOverlayBorder, bool fMarkup) |
| 455 | : Control(rtBounds), fDrawBackground(true), fDrawFrame(true), iPicPadding(iPicPadding), pLogBuffer(nullptr) |
| 456 | { |
| 457 | // calc client rect |
| 458 | UpdateOwnPos(); |
| 459 | // create content scroll window |
| 460 | pClientWindow = new ScrollWindow(this); |
| 461 | pClientWindow->SetBounds(GetContainedClientRect()); |
| 462 | // create content multiline label |
| 463 | pLogBuffer = new MultilineLabel(pClientWindow->GetContainedClientRect(), iMaxLines, iMaxTextLen, szIndentChars, fAutoGrow, fMarkup); |
| 464 | // add to scroll window |
| 465 | pClientWindow->AddElement(pChild: pLogBuffer); |
| 466 | // update scrolling (for empty buffer) |
| 467 | pClientWindow->SetClientHeight(1); |
| 468 | // create content picture, if desired |
| 469 | C4Rect rcContentSize = pClientWindow->GetClientRect(); |
| 470 | if (iPicWdt && iPicHgt) |
| 471 | { |
| 472 | C4Rect rcImage; |
| 473 | rcImage.x = std::max<int32_t>(a: rcContentSize.GetMiddleX() - iPicWdt / 2, b: 0); |
| 474 | rcImage.y = 0; |
| 475 | rcImage.Wdt = std::min<size_t>(a: iPicWdt, b: rcContentSize.Wdt); |
| 476 | rcImage.Hgt = iPicHgt * rcImage.Wdt / iPicWdt; |
| 477 | rcContentSize.y += rcImage.Hgt + iPicPadding; |
| 478 | if (pOverlayPic) |
| 479 | pTitlePicture = new OverlayPicture(rcImage, false, *pOverlayPic, iOverlayBorder); |
| 480 | else |
| 481 | pTitlePicture = new Picture(rcImage, false); |
| 482 | pClientWindow->AddElement(pChild: pTitlePicture); |
| 483 | } |
| 484 | else pTitlePicture = nullptr; |
| 485 | |
| 486 | // update size |
| 487 | UpdateSize(); |
| 488 | } |
| 489 | |
| 490 | void TextWindow::UpdateSize() |
| 491 | { |
| 492 | Control::UpdateSize(); |
| 493 | pClientWindow->SetBounds(GetContainedClientRect()); |
| 494 | // resize log buffer pos to horizontal extents |
| 495 | C4Rect rcChildBounds = pLogBuffer->GetBounds(); |
| 496 | rcChildBounds.x = 0; |
| 497 | rcChildBounds.y = pTitlePicture ? pTitlePicture->GetBounds().Hgt + iPicPadding : 0; |
| 498 | rcChildBounds.Wdt = pClientWindow->GetClientRect().Wdt; |
| 499 | pLogBuffer->SetBounds(rcChildBounds); |
| 500 | } |
| 501 | |
| 502 | void TextWindow::DrawElement(C4FacetEx &cgo) |
| 503 | { |
| 504 | // draw background |
| 505 | if (fDrawBackground) lpDDraw->DrawBoxDw(sfcDest: cgo.Surface, iX1: cgo.TargetX + rcBounds.x, iY1: cgo.TargetY + rcBounds.y, iX2: rcBounds.x + rcBounds.Wdt - 1 + cgo.TargetX, iY2: rcBounds.y + rcBounds.Hgt - 1 + cgo.TargetY, dwClr: 0x7f000000); |
| 506 | // draw frame |
| 507 | if (fDrawFrame) Draw3DFrame(cgo); |
| 508 | } |
| 509 | |
| 510 | void TextWindow::ElementSizeChanged(Element *pOfElement) |
| 511 | { |
| 512 | // inherited |
| 513 | if (pOfElement->GetParent() == this) |
| 514 | Control::ElementSizeChanged(pOfElement); |
| 515 | // update size of scroll control |
| 516 | if (pClientWindow && pLogBuffer) |
| 517 | pClientWindow->SetClientHeight(pLogBuffer->GetBounds().y + pLogBuffer->GetBounds().Hgt); |
| 518 | } |
| 519 | |
| 520 | void TextWindow::ElementPosChanged(Element *pOfElement) |
| 521 | { |
| 522 | // inherited |
| 523 | if (pOfElement->GetParent() == this) |
| 524 | Control::ElementSizeChanged(pOfElement); |
| 525 | // update size of scroll control |
| 526 | if (pClientWindow && pLogBuffer) |
| 527 | pClientWindow->SetClientHeight(pLogBuffer->GetBounds().y + pLogBuffer->GetBounds().Hgt); |
| 528 | } |
| 529 | |
| 530 | void TextWindow::SetPicture(const C4Facet &rNewPic) |
| 531 | { |
| 532 | // update picture |
| 533 | if (!pTitlePicture) return; |
| 534 | pTitlePicture->SetFacet(rNewPic); |
| 535 | // reposition multiline label below picture if any is assigned |
| 536 | pLogBuffer->GetBounds().y = rNewPic.Surface ? pTitlePicture->GetBounds().Hgt + iPicPadding : 0; |
| 537 | pLogBuffer->UpdateOwnPos(); |
| 538 | pTitlePicture->SetVisibility(!!rNewPic.Surface); |
| 539 | } |
| 540 | |
| 541 | } // end of namespace |
| 542 | |