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