| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) RedWolf Design |
| 5 | * Copyright (c) 2001, Sven2 |
| 6 | * Copyright (c) 2017-2021, 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 | // dialog base classes and some user dialogs |
| 20 | |
| 21 | #include "C4GuiDialogs.h" |
| 22 | #include "C4GuiEdit.h" |
| 23 | #include "C4GuiResource.h" |
| 24 | #include <C4Gui.h> |
| 25 | |
| 26 | #include <C4FullScreen.h> |
| 27 | #include <C4LoaderScreen.h> |
| 28 | #include <C4Application.h> |
| 29 | #include <C4Viewport.h> |
| 30 | #include <C4Console.h> |
| 31 | #include <C4Def.h> |
| 32 | #include <C4Wrappers.h> |
| 33 | |
| 34 | #include <StdGL.h> |
| 35 | |
| 36 | #include <format> |
| 37 | |
| 38 | #ifdef _WIN32 |
| 39 | #include "StdRegistry.h" |
| 40 | #include "res/engine_resource.h" |
| 41 | #endif |
| 42 | |
| 43 | namespace C4GUI |
| 44 | { |
| 45 | namespace |
| 46 | { |
| 47 | inline Button *newDlgCloseButton(const C4Rect &bounds) { return new CloseButton{LoadResStr(id: C4ResStrTableKey::IDS_DLG_CLOSE), bounds, true}; } |
| 48 | inline Button *newRetryButton(const C4Rect &bounds) { return new CloseButton{LoadResStr(id: C4ResStrTableKey::IDS_BTN_RETRY), bounds, true}; } |
| 49 | } |
| 50 | |
| 51 | // EM window class |
| 52 | class DialogWindow : public CStdWindow |
| 53 | { |
| 54 | #ifdef _WIN32 |
| 55 | private: |
| 56 | static constexpr auto WindowStyle = WS_VISIBLE | WS_POPUP | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX; |
| 57 | #endif |
| 58 | public: |
| 59 | DialogWindow(Dialog &dialog) : dialog{dialog} {} |
| 60 | virtual void Close() override; |
| 61 | |
| 62 | #ifdef _WIN32 |
| 63 | bool Init(CStdApp *app, const char *title, const class C4Rect &bounds, CStdWindow *parent = nullptr) override; |
| 64 | |
| 65 | std::pair<DWORD, DWORD> GetWindowStyle() const override { return {WindowStyle, 0}; } |
| 66 | WNDCLASSEX GetWindowClass(HINSTANCE instance) const override; |
| 67 | bool GetPositionData(std::string &id, std::string &subKey, bool &storeSize) const override; |
| 68 | #endif |
| 69 | |
| 70 | private: |
| 71 | Dialog &dialog; |
| 72 | |
| 73 | #ifdef _WIN32 |
| 74 | friend LRESULT APIENTRY DialogWinProc(HWND, UINT, WPARAM, LPARAM); |
| 75 | #endif |
| 76 | }; |
| 77 | |
| 78 | // FrameDecoration |
| 79 | |
| 80 | void FrameDecoration::Clear() |
| 81 | { |
| 82 | idSourceDef = 0; |
| 83 | dwBackClr = C4GUI_StandardBGColor; |
| 84 | iBorderTop = iBorderLeft = iBorderRight = iBorderBottom = 0; |
| 85 | fHasGfxOutsideClientArea = false; |
| 86 | fctTop.Default(); |
| 87 | fctTopRight.Default(); |
| 88 | fctRight.Default(); |
| 89 | fctBottomRight.Default(); |
| 90 | fctBottom.Default(); |
| 91 | fctBottomLeft.Default(); |
| 92 | fctLeft.Default(); |
| 93 | fctTopLeft.Default(); |
| 94 | } |
| 95 | |
| 96 | bool FrameDecoration::SetFacetByAction(C4Def *pOfDef, C4FacetEx &rfctTarget, const char *szFacetName) |
| 97 | { |
| 98 | // get action |
| 99 | const std::string actName{std::format(fmt: "FrameDeco{}" , args&: szFacetName)}; |
| 100 | int cnt; C4ActionDef *pAct = pOfDef->ActMap; |
| 101 | for (cnt = pOfDef->ActNum; cnt; --cnt, ++pAct) |
| 102 | if (actName == pAct->Name) |
| 103 | break; |
| 104 | if (!cnt) return false; |
| 105 | // set facet by it |
| 106 | rfctTarget.Set(nsfc: pOfDef->Graphics.GetBitmap(), nx: pAct->Facet.x, ny: pAct->Facet.y, nwdt: pAct->Facet.Wdt, nhgt: pAct->Facet.Hgt, ntx: pAct->Facet.tx, nty: pAct->Facet.ty); |
| 107 | return true; |
| 108 | } |
| 109 | |
| 110 | bool FrameDecoration::SetByDef(C4ID idSourceDef) |
| 111 | { |
| 112 | // get source def |
| 113 | C4Def *pSrcDef = C4Id2Def(id: idSourceDef); |
| 114 | if (!pSrcDef) return false; |
| 115 | // script compiled? |
| 116 | if (!pSrcDef->Script.IsReady()) return false; |
| 117 | // reset old |
| 118 | Clear(); |
| 119 | this->idSourceDef = idSourceDef; |
| 120 | // query values |
| 121 | dwBackClr = pSrcDef->Script.Call(szFunction: std::format(PSF_FrameDecoration, args: "BackClr" ).c_str()).getInt(); |
| 122 | iBorderTop = pSrcDef->Script.Call(szFunction: std::format(PSF_FrameDecoration, args: "BorderTop" ).c_str()).getInt(); |
| 123 | iBorderLeft = pSrcDef->Script.Call(szFunction: std::format(PSF_FrameDecoration, args: "BorderLeft" ).c_str()).getInt(); |
| 124 | iBorderRight = pSrcDef->Script.Call(szFunction: std::format(PSF_FrameDecoration, args: "BorderRight" ).c_str()).getInt(); |
| 125 | iBorderBottom = pSrcDef->Script.Call(szFunction: std::format(PSF_FrameDecoration, args: "BorderBottom" ).c_str()).getInt(); |
| 126 | // get gfx |
| 127 | SetFacetByAction(pOfDef: pSrcDef, rfctTarget&: fctTop, szFacetName: "Top" ); |
| 128 | SetFacetByAction(pOfDef: pSrcDef, rfctTarget&: fctTopRight, szFacetName: "TopRight" ); |
| 129 | SetFacetByAction(pOfDef: pSrcDef, rfctTarget&: fctRight, szFacetName: "Right" ); |
| 130 | SetFacetByAction(pOfDef: pSrcDef, rfctTarget&: fctBottomRight, szFacetName: "BottomRight" ); |
| 131 | SetFacetByAction(pOfDef: pSrcDef, rfctTarget&: fctBottom, szFacetName: "Bottom" ); |
| 132 | SetFacetByAction(pOfDef: pSrcDef, rfctTarget&: fctBottomLeft, szFacetName: "BottomLeft" ); |
| 133 | SetFacetByAction(pOfDef: pSrcDef, rfctTarget&: fctLeft, szFacetName: "Left" ); |
| 134 | SetFacetByAction(pOfDef: pSrcDef, rfctTarget&: fctTopLeft, szFacetName: "TopLeft" ); |
| 135 | // check for gfx outside main area |
| 136 | fHasGfxOutsideClientArea = (fctTopLeft.TargetY < 0) || (fctTop.TargetY < 0) || (fctTopRight.TargetY < 0) |
| 137 | || (fctTopLeft.TargetX < 0) || (fctLeft.TargetX < 0) || (fctBottomLeft.TargetX < 0) |
| 138 | || (fctTopRight.TargetX + fctTopRight.Wdt > iBorderRight) || (fctRight.TargetX + fctRight.Wdt > iBorderRight) || (fctBottomRight.TargetX + fctBottomRight.Wdt > iBorderRight) |
| 139 | || (fctBottomLeft.TargetY + fctBottomLeft.Hgt > iBorderBottom) || (fctBottom.TargetY + fctBottom.Hgt > iBorderBottom) || (fctBottomRight.TargetY + fctBottomRight.Hgt > iBorderBottom); |
| 140 | // k, done |
| 141 | return true; |
| 142 | } |
| 143 | |
| 144 | bool FrameDecoration::UpdateGfx() |
| 145 | { |
| 146 | // simply re-set by def |
| 147 | return SetByDef(idSourceDef); |
| 148 | } |
| 149 | |
| 150 | void FrameDecoration::Draw(C4FacetEx &cgo, C4Rect &rcBounds) |
| 151 | { |
| 152 | int ox = cgo.TargetX + rcBounds.x, oy = cgo.TargetY + rcBounds.y; |
| 153 | |
| 154 | // draw BG |
| 155 | lpDDraw->DrawBoxDw(sfcDest: cgo.Surface, iX1: ox, iY1: oy, iX2: ox + rcBounds.Wdt - 1, iY2: oy + rcBounds.Hgt - 1, dwClr: dwBackClr); |
| 156 | |
| 157 | const auto drawHorizontal = [this, &cgo, &rcBounds, ox](C4FacetEx facet, std::int32_t y) |
| 158 | { |
| 159 | if (facet.Wdt <= 0) |
| 160 | { |
| 161 | return; |
| 162 | } |
| 163 | |
| 164 | for (std::int32_t x = iBorderLeft; x < rcBounds.Wdt - iBorderRight; x += facet.Wdt) |
| 165 | { |
| 166 | int w = std::min<int>(a: facet.Wdt, b: rcBounds.Wdt - iBorderRight - x); |
| 167 | facet.Wdt = w; |
| 168 | facet.Draw(sfcTarget: cgo.Surface, iX: ox + x, iY: y + facet.TargetY); |
| 169 | } |
| 170 | }; |
| 171 | const auto drawVertical = [this, &cgo, &rcBounds, oy](C4FacetEx facet, std::int32_t x) |
| 172 | { |
| 173 | if (facet.Hgt <= 0) |
| 174 | { |
| 175 | return; |
| 176 | } |
| 177 | |
| 178 | for (std::int32_t y = iBorderTop; y < rcBounds.Hgt - iBorderBottom; y += facet.Hgt) |
| 179 | { |
| 180 | int h = std::min<int>(a: facet.Hgt, b: rcBounds.Hgt - iBorderBottom - y); |
| 181 | facet.Hgt = h; |
| 182 | facet.Draw(sfcTarget: cgo.Surface, iX: x + facet.TargetX, iY: oy + y); |
| 183 | } |
| 184 | }; |
| 185 | |
| 186 | // draw borders |
| 187 | drawHorizontal(fctTop, oy); |
| 188 | drawVertical(fctLeft, ox); |
| 189 | drawVertical(fctRight, ox + rcBounds.Wdt - iBorderRight); |
| 190 | drawHorizontal(fctBottom, oy + rcBounds.Hgt - iBorderBottom); |
| 191 | |
| 192 | // draw edges |
| 193 | fctTopLeft.Draw(sfcTarget: cgo.Surface, iX: ox + fctTopLeft.TargetX, iY: oy + fctTopLeft.TargetY); |
| 194 | fctTopRight.Draw(sfcTarget: cgo.Surface, iX: ox + rcBounds.Wdt - iBorderRight + fctTopRight.TargetX, iY: oy + fctTopRight.TargetY); |
| 195 | fctBottomLeft.Draw(sfcTarget: cgo.Surface, iX: ox + fctBottomLeft.TargetX, iY: oy + rcBounds.Hgt - iBorderBottom + fctBottomLeft.TargetY); |
| 196 | fctBottomRight.Draw(sfcTarget: cgo.Surface, iX: ox + rcBounds.Wdt - iBorderRight + fctBottomRight.TargetX, iY: oy + rcBounds.Hgt - iBorderBottom + fctBottomRight.TargetY); |
| 197 | } |
| 198 | |
| 199 | // DialogWindow |
| 200 | |
| 201 | #ifdef _WIN32 |
| 202 | |
| 203 | bool DialogWindow::Init(CStdApp *const app, const char *const title, const C4Rect &bounds, CStdWindow *const parent) |
| 204 | { |
| 205 | // calculate required size |
| 206 | RECT size{0, 0, bounds.Wdt, bounds.Hgt}; |
| 207 | if (!AdjustWindowRectEx(&size, WindowStyle, false, 0)) |
| 208 | { |
| 209 | return false; |
| 210 | } |
| 211 | |
| 212 | return CStdWindow::Init(app, title, {bounds.x, bounds.y, size.right - size.left, size.bottom - size.top}, parent); |
| 213 | } |
| 214 | |
| 215 | // Dialog |
| 216 | |
| 217 | LRESULT APIENTRY DialogWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) |
| 218 | { |
| 219 | if (uMsg != WM_NCCREATE) |
| 220 | { |
| 221 | Dialog *const dialog{&reinterpret_cast<DialogWindow *>(GetWindowLongPtr(hwnd, GWLP_USERDATA))->dialog}; |
| 222 | // Process message |
| 223 | switch (uMsg) |
| 224 | { |
| 225 | case WM_KEYDOWN: |
| 226 | if (Game.DoKeyboardInput(wParam, KEYEV_Down, !!(lParam & 0x20000000), Application.IsControlDown(), Application.IsShiftDown(), !!(lParam & 0x40000000), dialog)) return 0; |
| 227 | break; |
| 228 | |
| 229 | case WM_KEYUP: |
| 230 | if (Game.DoKeyboardInput(wParam, KEYEV_Up, !!(lParam & 0x20000000), Application.IsControlDown(), Application.IsShiftDown(), false, dialog)) return 0; |
| 231 | break; |
| 232 | |
| 233 | case WM_SYSKEYDOWN: |
| 234 | if (wParam == 18) break; |
| 235 | if (Game.DoKeyboardInput(wParam, KEYEV_Down, !!(lParam & 0x20000000), Application.IsControlDown(), Application.IsShiftDown(), !!(lParam & 0x40000000), dialog)) return 0; |
| 236 | break; |
| 237 | |
| 238 | case WM_CLOSE: |
| 239 | dialog->Close(false); |
| 240 | break; |
| 241 | |
| 242 | case WM_PAINT: |
| 243 | // 2do: only draw specific dlg? |
| 244 | break; |
| 245 | return 0; |
| 246 | |
| 247 | case WM_LBUTTONDOWN: Game.pGUI->MouseInput(C4MC_Button_LeftDown, LOWORD(lParam), HIWORD(lParam), wParam, dialog, nullptr); break; |
| 248 | case WM_LBUTTONUP: Game.pGUI->MouseInput(C4MC_Button_LeftUp, LOWORD(lParam), HIWORD(lParam), wParam, dialog, nullptr); break; |
| 249 | case WM_RBUTTONDOWN: Game.pGUI->MouseInput(C4MC_Button_RightDown, LOWORD(lParam), HIWORD(lParam), wParam, dialog, nullptr); break; |
| 250 | case WM_RBUTTONUP: Game.pGUI->MouseInput(C4MC_Button_RightUp, LOWORD(lParam), HIWORD(lParam), wParam, dialog, nullptr); break; |
| 251 | case WM_LBUTTONDBLCLK: Game.pGUI->MouseInput(C4MC_Button_LeftDouble, LOWORD(lParam), HIWORD(lParam), wParam, dialog, nullptr); break; |
| 252 | case WM_RBUTTONDBLCLK: Game.pGUI->MouseInput(C4MC_Button_RightDouble, LOWORD(lParam), HIWORD(lParam), wParam, dialog, nullptr); break; |
| 253 | |
| 254 | case WM_MOUSEMOVE: |
| 255 | Game.pGUI->MouseInput(C4MC_Button_None, LOWORD(lParam), HIWORD(lParam), wParam, dialog, nullptr); |
| 256 | break; |
| 257 | |
| 258 | case WM_MOUSEWHEEL: |
| 259 | Game.pGUI->MouseInput(C4MC_Button_Wheel, LOWORD(lParam), HIWORD(lParam), wParam, dialog, nullptr); |
| 260 | break; |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | return CStdWindow::DefaultWindowProc(hwnd, uMsg, wParam, lParam); |
| 265 | } |
| 266 | |
| 267 | WNDCLASSEX DialogWindow::GetWindowClass(const HINSTANCE instance) const |
| 268 | { |
| 269 | return { |
| 270 | .cbSize = sizeof(WNDCLASSEX), |
| 271 | .style = CS_DBLCLKS | CS_BYTEALIGNCLIENT, |
| 272 | .lpfnWndProc = &DialogWinProc, |
| 273 | .cbClsExtra = 0, |
| 274 | .cbWndExtra = 0, |
| 275 | .hInstance = instance, |
| 276 | .hIcon = LoadIcon(instance, MAKEINTRESOURCE(IDI_00_C4X)), |
| 277 | .hCursor = LoadCursor(nullptr, IDC_ARROW), |
| 278 | .hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BACKGROUND), |
| 279 | .lpszMenuName = nullptr, |
| 280 | .lpszClassName = L"C4GUIdlg" , // keep for backwards compatibility |
| 281 | .hIconSm = LoadIcon(instance, MAKEINTRESOURCE(IDI_00_C4X)) |
| 282 | }; |
| 283 | } |
| 284 | |
| 285 | bool DialogWindow::GetPositionData(std::string &id, std::string &subKey, bool &storeSize) const |
| 286 | { |
| 287 | if (const char *const dialogID{dialog.GetID()}; dialogID && *dialogID) |
| 288 | { |
| 289 | id = std::string{"ConsoleGUI_" } + dialogID; |
| 290 | subKey = Config.GetSubkeyPath("Console" ); |
| 291 | storeSize = true; |
| 292 | return true; |
| 293 | } |
| 294 | |
| 295 | return false; |
| 296 | } |
| 297 | |
| 298 | #endif // _WIN32 |
| 299 | |
| 300 | void DialogWindow::Close() |
| 301 | { |
| 302 | // FIXME: Close the dialog of this window |
| 303 | } |
| 304 | |
| 305 | bool Dialog::CreateConsoleWindow() |
| 306 | { |
| 307 | // already created? |
| 308 | if (pWindow) return true; |
| 309 | // create it! |
| 310 | pWindow = new DialogWindow(*this); |
| 311 | if (!pWindow->Init(app: &Application, title: TitleString.isNull() ? "???" : TitleString.getData(), bounds: rcBounds, parent: &Console)) |
| 312 | { |
| 313 | delete pWindow; |
| 314 | pWindow = nullptr; |
| 315 | return false; |
| 316 | } |
| 317 | // create rendering context |
| 318 | if (lpDDraw) pCtx = lpDDraw->CreateContext(pWindow, &Application); |
| 319 | return true; |
| 320 | } |
| 321 | |
| 322 | void Dialog::DestroyConsoleWindow() |
| 323 | { |
| 324 | if (pWindow) pWindow->Clear(); |
| 325 | delete pWindow; pWindow = nullptr; |
| 326 | delete pCtx; pCtx = nullptr; |
| 327 | } |
| 328 | |
| 329 | Dialog::Dialog(int32_t iWdt, int32_t iHgt, const char *szTitle, bool fViewportDlg) : |
| 330 | Window(), pTitle(nullptr), pCloseBtn(nullptr), fDelOnClose(false), fViewportDlg(fViewportDlg), pWindow(nullptr), pCtx(nullptr), pFrameDeco(nullptr) |
| 331 | { |
| 332 | // zero fields |
| 333 | pActiveCtrl = nullptr; |
| 334 | fShow = fOK = false; |
| 335 | iFade = 100; eFade = eFadeNone; |
| 336 | // add title |
| 337 | rcBounds.Wdt = iWdt; |
| 338 | SetTitle(szToTitle: szTitle); |
| 339 | // set size - calcs client rect as well |
| 340 | SetBounds(C4Rect(0, 0, iWdt, iHgt)); |
| 341 | // create key callbacks |
| 342 | C4CustomKey::CodeList Keys; |
| 343 | Keys.push_back(x: C4KeyCodeEx(K_TAB)); |
| 344 | if (Config.Controls.GamepadGuiControl) |
| 345 | { |
| 346 | Keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_Right))); |
| 347 | } |
| 348 | pKeyAdvanceControl = new C4KeyBinding(Keys, "GUIAdvanceFocus" , KEYSCOPE_Gui, |
| 349 | new DlgKeyCBEx<Dialog, bool>(*this, false, &Dialog::KeyAdvanceFocus), C4CustomKey::PRIO_Dlg); |
| 350 | Keys.clear(); |
| 351 | Keys.push_back(x: C4KeyCodeEx(K_TAB, KEYS_Shift)); |
| 352 | if (Config.Controls.GamepadGuiControl) |
| 353 | { |
| 354 | Keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_Left))); |
| 355 | } |
| 356 | pKeyAdvanceControlB = new C4KeyBinding(Keys, "GUIAdvanceFocusBack" , KEYSCOPE_Gui, |
| 357 | new DlgKeyCBEx<Dialog, bool>(*this, true, &Dialog::KeyAdvanceFocus), C4CustomKey::PRIO_Dlg); |
| 358 | Keys.clear(); |
| 359 | Keys.push_back(x: C4KeyCodeEx(KEY_Any, KEYS_Alt)); |
| 360 | Keys.push_back(x: C4KeyCodeEx(KEY_Any, C4KeyShiftState(KEYS_Alt | KEYS_Shift))); |
| 361 | pKeyHotkey = new C4KeyBinding(Keys, "GUIHotkey" , KEYSCOPE_Gui, |
| 362 | new DlgKeyCBPassKey<Dialog>(*this, &Dialog::KeyHotkey), C4CustomKey::PRIO_Ctrl); |
| 363 | Keys.clear(); |
| 364 | Keys.push_back(x: C4KeyCodeEx(K_RETURN)); |
| 365 | if (Config.Controls.GamepadGuiControl) |
| 366 | { |
| 367 | Keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_AnyLowButton))); |
| 368 | } |
| 369 | pKeyEnter = new C4KeyBinding(Keys, "GUIDialogOkay" , KEYSCOPE_Gui, |
| 370 | new DlgKeyCB<Dialog>(*this, &Dialog::KeyEnter), C4CustomKey::PRIO_Dlg); |
| 371 | Keys.clear(); |
| 372 | Keys.push_back(x: C4KeyCodeEx(K_ESCAPE)); |
| 373 | if (Config.Controls.GamepadGuiControl) |
| 374 | { |
| 375 | Keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_AnyHighButton))); |
| 376 | } |
| 377 | pKeyEscape = new C4KeyBinding(Keys, "GUIDialogAbort" , KEYSCOPE_Gui, |
| 378 | new DlgKeyCB<Dialog>(*this, &Dialog::KeyEscape), C4CustomKey::PRIO_Dlg); |
| 379 | Keys.clear(); |
| 380 | Keys.push_back(x: C4KeyCodeEx(KEY_Any)); |
| 381 | Keys.push_back(x: C4KeyCodeEx(KEY_Any, KEYS_Shift)); |
| 382 | pKeyFocusDefControl = new C4KeyBinding(Keys, "GUIFocusDefault" , KEYSCOPE_Gui, |
| 383 | new DlgKeyCB<Dialog>(*this, &Dialog::KeyFocusDefault), C4CustomKey::PRIO_Dlg); |
| 384 | } |
| 385 | |
| 386 | void Dialog::SetTitle(const char *szTitle, bool fShowCloseButton) |
| 387 | { |
| 388 | // always keep local copy of title |
| 389 | TitleString.Copy(pnData: szTitle); |
| 390 | // console mode dialogs: Use window bar |
| 391 | if (!Application.isFullScreen && !IsViewportDialog()) |
| 392 | { |
| 393 | if (pWindow) pWindow->SetTitle(szTitle ? szTitle : "" ); |
| 394 | return; |
| 395 | } |
| 396 | // set new |
| 397 | if (szTitle && *szTitle) |
| 398 | { |
| 399 | int32_t iTextHgt = WoodenLabel::GetDefaultHeight(pUseFont: &GetRes()->TextFont); |
| 400 | if (pTitle) |
| 401 | { |
| 402 | pTitle->GetBounds() = C4Rect(-GetMarginLeft(), -iTextHgt, rcBounds.Wdt, iTextHgt); |
| 403 | // noupdate if title is same - this is necessary to prevent scrolling reset when refilling internal menus |
| 404 | if (SEqual(szStr1: pTitle->GetText(), szStr2: szTitle)) return; |
| 405 | pTitle->SetText(szText: szTitle); |
| 406 | } |
| 407 | else |
| 408 | AddElement(pChild: pTitle = new WoodenLabel(szTitle, C4Rect(-GetMarginLeft(), -iTextHgt, rcBounds.Wdt, iTextHgt), C4GUI_CaptionFontClr, &GetRes()->TextFont, ALeft, false)); |
| 409 | pTitle->SetToolTip(szTitle); |
| 410 | pTitle->SetDragTarget(this); |
| 411 | pTitle->SetAutoScrollTime(C4GUI_TitleAutoScrollTime); |
| 412 | if (fShowCloseButton) |
| 413 | { |
| 414 | pTitle->SetRightIndent(20); // for close button |
| 415 | if (!pCloseBtn) |
| 416 | { |
| 417 | AddElement(pChild: pCloseBtn = new CallbackButton<Dialog, IconButton>(Ico_Close, pTitle->GetToprightCornerRect(iWidth: 16, iHeight: 16, iHIndent: 4, iVIndent: 4, iIndexX: 0), 0, &Dialog::OnUserClose)); |
| 418 | pCloseBtn->SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_MNU_CLOSE)); |
| 419 | } |
| 420 | else |
| 421 | pCloseBtn->GetBounds() = pTitle->GetToprightCornerRect(iWidth: 16, iHeight: 16, iHIndent: 4, iVIndent: 4, iIndexX: 0); |
| 422 | } |
| 423 | } |
| 424 | else |
| 425 | { |
| 426 | delete pTitle; pTitle = nullptr; |
| 427 | delete pCloseBtn; pCloseBtn = nullptr; |
| 428 | } |
| 429 | } |
| 430 | |
| 431 | Dialog::~Dialog() |
| 432 | { |
| 433 | // kill key bindings |
| 434 | delete pKeyAdvanceControl; |
| 435 | delete pKeyAdvanceControlB; |
| 436 | delete pKeyHotkey; |
| 437 | delete pKeyEscape; |
| 438 | delete pKeyEnter; |
| 439 | delete pKeyFocusDefControl; |
| 440 | // clear window |
| 441 | DestroyConsoleWindow(); |
| 442 | // avoid endless delete/close-recursion |
| 443 | fDelOnClose = false; |
| 444 | // free deco |
| 445 | if (pFrameDeco) pFrameDeco->Deref(); |
| 446 | } |
| 447 | |
| 448 | void Dialog::UpdateSize() |
| 449 | { |
| 450 | // update title bar position |
| 451 | if (pTitle) |
| 452 | { |
| 453 | int32_t iTextHgt = WoodenLabel::GetDefaultHeight(pUseFont: &GetRes()->TextFont); |
| 454 | pTitle->SetBounds(C4Rect(-GetMarginLeft(), -iTextHgt, rcBounds.Wdt, iTextHgt)); |
| 455 | if (pCloseBtn) pCloseBtn->SetBounds(pTitle->GetToprightCornerRect(iWidth: 16, iHeight: 16, iHIndent: 4, iVIndent: 4, iIndexX: 0)); |
| 456 | } |
| 457 | // inherited |
| 458 | Window::UpdateSize(); |
| 459 | // update assigned window |
| 460 | if (pWindow) |
| 461 | { |
| 462 | auto wdt = rcBounds.Wdt, hgt = rcBounds.Hgt; |
| 463 | #ifdef _WIN32 |
| 464 | RECT rect{0, 0, wdt, hgt}; |
| 465 | if (::AdjustWindowRectEx(&rect, ConsoleDlgWindowStyle, false, 0)) |
| 466 | { |
| 467 | wdt = rect.right - rect.left; |
| 468 | hgt = rect.bottom - rect.top; |
| 469 | } |
| 470 | #endif |
| 471 | pWindow->SetSize(cx: wdt, cy: hgt); |
| 472 | } |
| 473 | } |
| 474 | |
| 475 | void Dialog::RemoveElement(Element *pChild) |
| 476 | { |
| 477 | // inherited |
| 478 | Window::RemoveElement(pChild); |
| 479 | // clear ptr |
| 480 | if (pChild == pActiveCtrl) pActiveCtrl = nullptr; |
| 481 | } |
| 482 | |
| 483 | void Dialog::Draw(C4FacetEx &cgo) |
| 484 | { |
| 485 | #ifndef USE_CONSOLE |
| 486 | // select rendering context |
| 487 | if (pCtx) if (!pCtx->Select()) return; |
| 488 | #endif |
| 489 | Screen *pScreen; |
| 490 | // evaluate fading |
| 491 | switch (eFade) |
| 492 | { |
| 493 | case eFadeNone: break; // no fading |
| 494 | case eFadeIn: |
| 495 | // fade in |
| 496 | if ((iFade += 10) >= 100) |
| 497 | { |
| 498 | if (pScreen = GetScreen()) |
| 499 | { |
| 500 | if (pScreen->GetTopDialog() == this) |
| 501 | pScreen->ActivateDialog(pDlg: this); |
| 502 | } |
| 503 | eFade = eFadeNone; |
| 504 | } |
| 505 | break; |
| 506 | case eFadeOut: |
| 507 | // fade out |
| 508 | if ((iFade -= 10) <= 0) |
| 509 | { |
| 510 | fVisible = fShow = false; |
| 511 | if (pScreen = GetScreen()) |
| 512 | pScreen->RecheckActiveDialog(); |
| 513 | eFade = eFadeNone; |
| 514 | } |
| 515 | } |
| 516 | // set fade |
| 517 | if (iFade < 100) |
| 518 | { |
| 519 | if (iFade <= 0) return; |
| 520 | lpDDraw->ActivateBlitModulation(dwWithClr: ((100 - iFade) * 255 / 100) << 24 | 0xffffff); |
| 521 | } |
| 522 | // separate window: Clear background |
| 523 | if (pWindow) |
| 524 | lpDDraw->DrawBoxDw(sfcDest: cgo.Surface, iX1: rcBounds.x, iY1: rcBounds.y, iX2: rcBounds.Wdt - 1, iY2: rcBounds.Hgt - 1, C4GUI_StandardBGColor & 0xffffff); |
| 525 | // draw window + contents (evaluates IsVisible) |
| 526 | Window::Draw(cgo); |
| 527 | // reset blit modulation |
| 528 | if (iFade < 100) lpDDraw->DeactivateBlitModulation(); |
| 529 | // blit output to own window |
| 530 | if (pWindow) Application.DDraw->PageFlip(); |
| 531 | #ifndef USE_CONSOLE |
| 532 | // switch back to original context |
| 533 | if (pCtx) pGL->GetMainCtx().Select(); |
| 534 | #endif |
| 535 | } |
| 536 | |
| 537 | void Dialog::DrawElement(C4FacetEx &cgo) |
| 538 | { |
| 539 | // custom border? |
| 540 | if (pFrameDeco) |
| 541 | pFrameDeco->Draw(cgo, rcBounds); |
| 542 | else |
| 543 | { |
| 544 | // standard border/bg then |
| 545 | // draw background |
| 546 | 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, C4GUI_StandardBGColor); |
| 547 | // draw frame |
| 548 | Draw3DFrame(cgo); |
| 549 | } |
| 550 | } |
| 551 | |
| 552 | bool Dialog::CharIn(const char *c) |
| 553 | { |
| 554 | // reroute to active control |
| 555 | if (pActiveCtrl && pActiveCtrl->CharIn(c)) return true; |
| 556 | // unprocessed: Focus default control |
| 557 | // Except for space, which may have been processed as a key already |
| 558 | // (changing focus here would render buttons unusable, because they switch on KeyUp) |
| 559 | Control *pDefCtrl = GetDefaultControl(); |
| 560 | if (pDefCtrl && pDefCtrl != pActiveCtrl && (!c || *c != 0x20)) |
| 561 | { |
| 562 | SetFocus(pCtrl: pDefCtrl, fByMouse: false); |
| 563 | if (pActiveCtrl && pActiveCtrl->CharIn(c)) |
| 564 | return true; |
| 565 | } |
| 566 | return false; |
| 567 | } |
| 568 | |
| 569 | bool Dialog::KeyHotkey(C4KeyCodeEx key) |
| 570 | { |
| 571 | #ifdef USE_SDL_MAINLOOP |
| 572 | const auto wKey = SDL_GetKeyName(SDL_GetKeyFromScancode(static_cast<SDL_Scancode>(key.Key)))[0]; |
| 573 | #else |
| 574 | uint16_t wKey = uint16_t(key.Key); |
| 575 | #endif |
| 576 | |
| 577 | // do hotkey procs for standard alphanumerics only |
| 578 | if (Inside<uint16_t>(TOUPPERIFX11(wKey), lbound: 'A', rbound: 'Z')) if (OnHotkey(cHotkey: char(TOUPPERIFX11(wKey)))) return true; |
| 579 | if (Inside<uint16_t>(TOUPPERIFX11(wKey), lbound: '0', rbound: '9')) if (OnHotkey(cHotkey: char(TOUPPERIFX11(wKey)))) return true; |
| 580 | return false; |
| 581 | } |
| 582 | |
| 583 | bool Dialog::KeyFocusDefault() |
| 584 | { |
| 585 | // unprocessed key: Focus default control |
| 586 | Control *pDefCtrl = GetDefaultControl(); |
| 587 | if (pDefCtrl && pDefCtrl != pActiveCtrl) |
| 588 | SetFocus(pCtrl: pDefCtrl, fByMouse: false); |
| 589 | // never mark this as processed, so a later char message to the control may be sent (for deselected chat) |
| 590 | return false; |
| 591 | } |
| 592 | |
| 593 | void Dialog::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, uint32_t dwKeyParam) |
| 594 | { |
| 595 | // inherited will do... |
| 596 | Window::MouseInput(rMouse, iButton, iX, iY, dwKeyParam); |
| 597 | } |
| 598 | |
| 599 | void Dialog::SetFocus(Control *pCtrl, bool fByMouse) |
| 600 | { |
| 601 | // no change? |
| 602 | if (pCtrl == pActiveCtrl) return; |
| 603 | // leave old focus |
| 604 | if (pActiveCtrl) |
| 605 | { |
| 606 | Control *pC = pActiveCtrl; |
| 607 | pActiveCtrl = nullptr; |
| 608 | pC->OnLooseFocus(); |
| 609 | // if leaving the old focus set a new one, abort here because it looks like the control didn't want to lose focus |
| 610 | if (pActiveCtrl) return; |
| 611 | } |
| 612 | // set new |
| 613 | if (pActiveCtrl = pCtrl) pCtrl->OnGetFocus(fByMouse); |
| 614 | } |
| 615 | |
| 616 | void Dialog::AdvanceFocus(bool fBackwards) |
| 617 | { |
| 618 | // get element to start from |
| 619 | Element *pCurrElement = pActiveCtrl; |
| 620 | // find new control |
| 621 | for (;;) |
| 622 | { |
| 623 | // get next element |
| 624 | pCurrElement = GetNextNestedElement(pPrevElement: pCurrElement, fBackwards); |
| 625 | // end reached: start from beginning |
| 626 | if (!pCurrElement && pActiveCtrl) if (!(pCurrElement = GetNextNestedElement(pPrevElement: nullptr, fBackwards))) return; |
| 627 | // cycled? |
| 628 | if (pCurrElement == pActiveCtrl) |
| 629 | { |
| 630 | // but current is no longer a focus element? Then defocus it and return |
| 631 | if (pCurrElement && !pCurrElement->IsFocusElement()) |
| 632 | SetFocus(pCtrl: nullptr, fByMouse: false); |
| 633 | return; |
| 634 | } |
| 635 | // for list elements, check whether the child can be selected |
| 636 | if (pCurrElement->GetParent() && !pCurrElement->GetParent()->IsSelectedChild(pChild: pCurrElement)) continue; |
| 637 | // check if this is a new control |
| 638 | Control *pFocusCtrl = pCurrElement->IsFocusElement(); |
| 639 | if (pFocusCtrl && pFocusCtrl != pActiveCtrl && pFocusCtrl->IsVisible()) |
| 640 | { |
| 641 | // set focus here... |
| 642 | SetFocus(pCtrl: pFocusCtrl, fByMouse: false); |
| 643 | // ...done! |
| 644 | return; |
| 645 | } |
| 646 | } |
| 647 | // never reached |
| 648 | } |
| 649 | |
| 650 | bool Dialog::Show(Screen *pOnScreen, bool fCB) |
| 651 | { |
| 652 | // already shown? |
| 653 | if (fShow) return false; |
| 654 | // default screen |
| 655 | if (!pOnScreen) if (!(pOnScreen = Screen::GetScreenS())) return false; |
| 656 | // show there |
| 657 | pOnScreen->ShowDialog(pDlg: this, fFade: false); |
| 658 | fVisible = true; |
| 659 | // developer mode: Create window |
| 660 | if (!Application.isFullScreen && !IsViewportDialog()) |
| 661 | if (!CreateConsoleWindow()) return false; |
| 662 | // CB |
| 663 | if (fCB) OnShown(); |
| 664 | return true; |
| 665 | } |
| 666 | |
| 667 | void Dialog::Close(bool fOK) |
| 668 | { |
| 669 | // already closed? |
| 670 | if (!fShow) return; |
| 671 | // set OK flag |
| 672 | this->fOK = fOK; |
| 673 | // get screen |
| 674 | Screen *pScreen = GetScreen(); |
| 675 | if (pScreen) pScreen->CloseDialog(pDlg: this, fFade: false); else fShow = false; |
| 676 | // developer mode: Remove window |
| 677 | if (pWindow) DestroyConsoleWindow(); |
| 678 | // do callback - last call, because it might do perilous things |
| 679 | OnClosed(fOK); |
| 680 | } |
| 681 | |
| 682 | void Dialog::OnClosed(bool fOK) |
| 683 | { |
| 684 | // developer mode: Remove window |
| 685 | if (pWindow) DestroyConsoleWindow(); |
| 686 | // delete when closing? |
| 687 | if (fDelOnClose) |
| 688 | { |
| 689 | fDelOnClose = false; |
| 690 | delete this; |
| 691 | } |
| 692 | } |
| 693 | |
| 694 | bool Dialog::DoModal() |
| 695 | { |
| 696 | // main message loop |
| 697 | while (fShow) |
| 698 | { |
| 699 | int32_t iResult = 1; |
| 700 | while ((iResult != HR_Timer) && fShow) |
| 701 | { |
| 702 | // dialog idle proc |
| 703 | OnIdle(); |
| 704 | // handle messages - this may block until the next timer |
| 705 | iResult = Application.HandleMessage(); |
| 706 | // quit |
| 707 | if (iResult == HR_Failure || !IsGUIValid()) return false; // game GUI and lobby will deleted in Game::Clear() |
| 708 | } |
| 709 | // Idle proc may have done something nasty |
| 710 | if (!IsGUIValid()) return false; |
| 711 | } |
| 712 | // return whether dlg was OK |
| 713 | return fOK; |
| 714 | } |
| 715 | |
| 716 | bool Dialog::Execute() |
| 717 | { |
| 718 | // process messages |
| 719 | int32_t iResult; |
| 720 | while ((iResult = Application.HandleMessage()) == HR_Message) |
| 721 | // check status |
| 722 | if (!IsGUIValid() || !fShow) return false; |
| 723 | if (iResult == HR_Failure) return false; |
| 724 | // check status |
| 725 | if (!IsGUIValid() || !fShow) return false; |
| 726 | return true; |
| 727 | } |
| 728 | |
| 729 | bool Dialog::IsActive(bool fForKeyboard) |
| 730 | { |
| 731 | // must be fully visible |
| 732 | if (!IsShown() || IsFading()) return false; |
| 733 | // screen-less dialogs are always inactive (not yet added) |
| 734 | Screen *pScreen = GetScreen(); |
| 735 | if (!pScreen) return false; |
| 736 | // no keyboard focus if screen is in context mode |
| 737 | if (fForKeyboard && pScreen->HasContext()) return false; |
| 738 | // always okay in shared mode: all dlgs accessible by mouse |
| 739 | if (!pScreen->IsExclusive() && !fForKeyboard) return true; |
| 740 | // exclusive mode or keyboard input: Only one dlg active |
| 741 | return pScreen->pActiveDlg == this; |
| 742 | } |
| 743 | |
| 744 | bool Dialog::FadeIn(Screen *pOnScreen) |
| 745 | { |
| 746 | // default screen |
| 747 | if (!pOnScreen) if (!(pOnScreen = Screen::GetScreenS())) return false; |
| 748 | // fade in there |
| 749 | pOnScreen->ShowDialog(pDlg: this, fFade: true); |
| 750 | iFade = 0; |
| 751 | eFade = eFadeIn; |
| 752 | fVisible = true; |
| 753 | OnShown(); |
| 754 | // done, success |
| 755 | return true; |
| 756 | } |
| 757 | |
| 758 | void Dialog::FadeOut(bool fCloseWithOK) |
| 759 | { |
| 760 | // only if shown, or being faded in |
| 761 | if (!IsShown() && (!fVisible || eFade != eFadeIn)) return; |
| 762 | // set OK flag |
| 763 | this->fOK = fCloseWithOK; |
| 764 | // fade out |
| 765 | Screen *pOnScreen = GetScreen(); |
| 766 | if (!pOnScreen) return; |
| 767 | pOnScreen->CloseDialog(pDlg: this, fFade: true); |
| 768 | eFade = eFadeOut; |
| 769 | // do callback - last call, because it might do perilous things |
| 770 | OnClosed(fOK: fCloseWithOK); |
| 771 | } |
| 772 | |
| 773 | void Dialog::ApplyElementOffset(int32_t &riX, int32_t &riY) |
| 774 | { |
| 775 | // inherited |
| 776 | Window::ApplyElementOffset(riX, riY); |
| 777 | // apply viewport offset, if a viewport is assigned |
| 778 | C4Viewport *pVP = GetViewport(); |
| 779 | if (pVP) |
| 780 | { |
| 781 | C4Rect rcVP(pVP->GetOutputRect()); |
| 782 | riX -= rcVP.x; riY -= rcVP.y; |
| 783 | } |
| 784 | } |
| 785 | |
| 786 | void Dialog::ApplyInvElementOffset(int32_t &riX, int32_t &riY) |
| 787 | { |
| 788 | // inherited |
| 789 | Window::ApplyInvElementOffset(riX, riY); |
| 790 | // apply viewport offset, if a viewport is assigned |
| 791 | C4Viewport *pVP = GetViewport(); |
| 792 | if (pVP) |
| 793 | { |
| 794 | C4Rect rcVP(pVP->GetOutputRect()); |
| 795 | riX += rcVP.x; riY += rcVP.y; |
| 796 | } |
| 797 | } |
| 798 | |
| 799 | void Dialog::SetClientSize(int32_t iToWdt, int32_t iToHgt) |
| 800 | { |
| 801 | // calc new bounds |
| 802 | iToWdt += GetMarginLeft() + GetMarginRight(); |
| 803 | iToHgt += GetMarginTop() + GetMarginBottom(); |
| 804 | rcBounds.x += (rcBounds.Wdt - iToWdt) / 2; |
| 805 | rcBounds.y += (rcBounds.Hgt - iToHgt) / 2; |
| 806 | rcBounds.Wdt = iToWdt; rcBounds.Hgt = iToHgt; |
| 807 | // reflect changes |
| 808 | UpdatePos(); |
| 809 | } |
| 810 | |
| 811 | // FullscreenDialog |
| 812 | |
| 813 | FullscreenDialog::FullscreenDialog(const char *szTitle, const char *szSubtitle) |
| 814 | : Dialog(Screen::GetScreenS()->GetClientRect().Wdt, Screen::GetScreenS()->GetClientRect().Hgt, nullptr /* create own title */, false), pFullscreenTitle(nullptr), pBtnHelp(nullptr) |
| 815 | { |
| 816 | // set margins |
| 817 | int32_t iScreenX = Screen::GetScreenS()->GetClientRect().Wdt; |
| 818 | int32_t iScreenY = Screen::GetScreenS()->GetClientRect().Hgt; |
| 819 | if (iScreenX < 500) iDlgMarginX = 2; else iDlgMarginX = iScreenX / 50; |
| 820 | if (iScreenY < 320) iDlgMarginY = 2; else iDlgMarginY = iScreenY * 2 / 75; |
| 821 | // set size - calcs client rect as well |
| 822 | SetBounds(C4Rect(0, 0, iScreenX, iScreenY)); |
| 823 | // create title |
| 824 | SetTitle(szTitle); |
| 825 | // create subtitle (only with upperboard) |
| 826 | if (szSubtitle && *szSubtitle && HasUpperBoard()) |
| 827 | { |
| 828 | AddElement(pChild: pSubTitle = new Label(szSubtitle, rcClientRect.Wdt, C4UpperBoardHeight - GetRes()->CaptionFont.GetLineHeight() / 2 - 25 - GetMarginTop(), ARight, C4GUI_CaptionFontClr, &GetRes()->TextFont)); |
| 829 | pSubTitle->SetToolTip(szTitle); |
| 830 | } |
| 831 | else pSubTitle = nullptr; |
| 832 | } |
| 833 | |
| 834 | void FullscreenDialog::SetTitle(const char *szTitle) |
| 835 | { |
| 836 | delete pFullscreenTitle; pFullscreenTitle = nullptr; |
| 837 | // change title text; creates or removes title bar if necessary |
| 838 | if (szTitle && *szTitle) |
| 839 | { |
| 840 | // not using dlg label, which is a wooden label |
| 841 | if (HasUpperBoard()) |
| 842 | pFullscreenTitle = new Label(szTitle, 0, C4UpperBoardHeight / 2 - GetRes()->TitleFont.GetLineHeight() / 2 - GetMarginTop(), ALeft, C4GUI_CaptionFontClr, &GetRes()->TitleFont); |
| 843 | else |
| 844 | // non-woodbar: Title is centered and in big font |
| 845 | pFullscreenTitle = new Label(szTitle, GetClientRect().Wdt / 2, C4UpperBoardHeight / 2 - GetRes()->TitleFont.GetLineHeight() / 2 - GetMarginTop(), ACenter, C4GUI_FullscreenCaptionFontClr, &GetRes()->TitleFont); |
| 846 | AddElement(pChild: pFullscreenTitle); |
| 847 | pFullscreenTitle->SetToolTip(szTitle); |
| 848 | } |
| 849 | } |
| 850 | |
| 851 | void FullscreenDialog::DrawElement(C4FacetEx &cgo) |
| 852 | { |
| 853 | // draw upper board |
| 854 | if (HasUpperBoard()) |
| 855 | lpDDraw->BlitSurfaceTile(sfcSurface: Game.GraphicsResource.fctUpperBoard.Surface, sfcTarget: cgo.Surface, iToX: 0, iToY: std::min<int32_t>(a: iFade - Game.GraphicsResource.fctUpperBoard.Hgt, b: 0), iToWdt: cgo.Wdt, iToHgt: Game.GraphicsResource.fctUpperBoard.Hgt); |
| 856 | } |
| 857 | |
| 858 | int32_t FullscreenDialog::GetMarginTop() |
| 859 | { |
| 860 | return (HasUpperBoard() ? C4UpperBoard::Height() : C4GUI_FullscreenDlg_TitleHeight) |
| 861 | + iDlgMarginY; |
| 862 | } |
| 863 | |
| 864 | void FullscreenDialog::UpdateOwnPos() |
| 865 | { |
| 866 | // inherited to update client rect |
| 867 | Dialog::UpdateOwnPos(); |
| 868 | // reposition help button |
| 869 | UpdateHelpButtonPos(); |
| 870 | } |
| 871 | |
| 872 | void FullscreenDialog::UpdateHelpButtonPos() |
| 873 | { |
| 874 | // reposition help button |
| 875 | if (pBtnHelp) pBtnHelp->SetBounds(C4Rect(GetBounds().Wdt - 4 - 32 - GetMarginLeft(), 4 - GetMarginTop(), 32, 32)); |
| 876 | } |
| 877 | |
| 878 | void FullscreenDialog::DrawBackground(C4FacetEx &cgo, C4Facet &rFromFct) |
| 879 | { |
| 880 | // draw across fullscreen bounds - zoom 1px border to prevent flashing borders by blit offsets |
| 881 | Screen *pScr = GetScreen(); |
| 882 | C4Facet cgoScreen = cgo; |
| 883 | C4Rect &rcScreenBounds = pScr ? pScr->GetBounds() : GetBounds(); |
| 884 | cgoScreen.X = rcScreenBounds.x - 1; cgoScreen.Y = rcScreenBounds.y - 1; |
| 885 | cgoScreen.Wdt = rcScreenBounds.Wdt + 2; cgoScreen.Hgt = rcScreenBounds.Hgt + 2; |
| 886 | rFromFct.DrawFullScreen(cgo&: cgoScreen); |
| 887 | } |
| 888 | |
| 889 | // MessageDialog |
| 890 | |
| 891 | MessageDialog::MessageDialog(const char *szMessage, const char *szCaption, uint32_t dwButtons, Icons icoIcon, DlgSize eSize, bool *piConfigDontShowAgainSetting, bool fDefaultNo, int32_t zOrdering) |
| 892 | : Dialog(eSize, 100 /* will be resized */, szCaption, false), piConfigDontShowAgainSetting(piConfigDontShowAgainSetting), zOrdering(zOrdering) |
| 893 | { |
| 894 | CStdFont &rUseFont = GetRes()->TextFont; |
| 895 | // get positions |
| 896 | ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true); |
| 897 | // place icon |
| 898 | C4Rect rcIcon = caMain.GetFromLeft(C4GUI_IconWdt); rcIcon.Hgt = C4GUI_IconHgt; |
| 899 | Icon *pIcon = new Icon(rcIcon, icoIcon); AddElement(pChild: pIcon); |
| 900 | // centered text for small dialogs and/or dialogs w/o much text (i.e.: no linebreaks) |
| 901 | bool fTextCentered; |
| 902 | if (eSize != dsRegular) |
| 903 | fTextCentered = true; |
| 904 | else |
| 905 | { |
| 906 | int32_t iMsgWdt = 0, iMsgHgt = 0; |
| 907 | rUseFont.GetTextExtent(szText: szMessage, rsx&: iMsgWdt, rsy&: iMsgHgt); |
| 908 | fTextCentered = ((iMsgWdt <= caMain.GetInnerWidth() - C4GUI_IconWdt - C4GUI_DefDlgIndent * 2) && iMsgHgt <= rUseFont.GetLineHeight()); |
| 909 | } |
| 910 | // centered text dialog: waste some icon space on the right to balance dialog |
| 911 | if (fTextCentered) caMain.GetFromRight(C4GUI_IconWdt); |
| 912 | // place message label |
| 913 | // use text with line breaks |
| 914 | StdStrBuf sMsgBroken; |
| 915 | int iMsgHeight = rUseFont.BreakMessage(szMsg: szMessage, iWdt: caMain.GetInnerWidth(), pOut: &sMsgBroken, fCheckMarkup: true); |
| 916 | lblText = new Label("" , caMain.GetFromTop(iHgt: iMsgHeight), fTextCentered ? ACenter : ALeft, C4GUI_MessageFontClr, &rUseFont, false); |
| 917 | lblText->SetText(szText: sMsgBroken.getData(), fAllowHotkey: false); |
| 918 | AddElement(pChild: lblText); |
| 919 | // place do-not-show-again-checkbox |
| 920 | if (piConfigDontShowAgainSetting) |
| 921 | { |
| 922 | int w = 100, h = 20; |
| 923 | const char *szCheckText = LoadResStr(id: C4ResStrTableKey::IDS_MSG_DONTSHOW); |
| 924 | CheckBox::GetStandardCheckBoxSize(piWdt: &w, piHgt: &h, szForCaptionText: szCheckText, pUseFont: nullptr); |
| 925 | CheckBox *pCheck = new C4GUI::CheckBox(caMain.GetFromTop(iHgt: h, iWdt: w), szCheckText, !!*piConfigDontShowAgainSetting); |
| 926 | pCheck->SetOnChecked(new C4GUI::CallbackHandler<MessageDialog>(this, &MessageDialog::OnDontShowAgainCheck)); |
| 927 | AddElement(pChild: pCheck); |
| 928 | } |
| 929 | if (!fTextCentered) caMain.ExpandLeft(C4GUI_DefDlgIndent * 2 + C4GUI_IconWdt); |
| 930 | // place button(s) |
| 931 | ComponentAligner caButtonArea(caMain.GetFromTop(C4GUI_ButtonAreaHgt), 0, 0); |
| 932 | int32_t iButtonCount = 0; |
| 933 | int32_t i = 1; while (i) { if (dwButtons & i) ++iButtonCount; i = i << 1; } |
| 934 | fHasOK = !!(dwButtons & btnOK) || !!(dwButtons & btnYes); |
| 935 | Button *btnFocus = nullptr; |
| 936 | if (iButtonCount) |
| 937 | { |
| 938 | C4Rect rcBtn = caButtonArea.GetCentered(iWdt: iButtonCount * C4GUI_DefButton2Wdt + (iButtonCount - 1) * C4GUI_DefButton2HSpace, C4GUI_ButtonHgt); |
| 939 | rcBtn.Wdt = C4GUI_DefButton2Wdt; |
| 940 | // OK |
| 941 | if (dwButtons & btnOK) |
| 942 | { |
| 943 | Button *pBtnOK = newOKButton(bounds: rcBtn); |
| 944 | AddElement(pChild: pBtnOK); |
| 945 | rcBtn.x += C4GUI_DefButton2Wdt + C4GUI_DefButton2HSpace; |
| 946 | if (!fDefaultNo) btnFocus = pBtnOK; |
| 947 | } |
| 948 | // Retry |
| 949 | if (dwButtons & btnRetry) |
| 950 | { |
| 951 | Button *pBtnRetry = newRetryButton(bounds: rcBtn); |
| 952 | AddElement(pChild: pBtnRetry); |
| 953 | rcBtn.x += C4GUI_DefButton2Wdt + C4GUI_DefButton2HSpace; |
| 954 | if (!btnFocus) btnFocus = pBtnRetry; |
| 955 | } |
| 956 | // Cancel |
| 957 | if (dwButtons & btnAbort) |
| 958 | { |
| 959 | Button *pBtnAbort = newCancelButton(bounds: rcBtn); |
| 960 | AddElement(pChild: pBtnAbort); |
| 961 | rcBtn.x += C4GUI_DefButton2Wdt + C4GUI_DefButton2HSpace; |
| 962 | if (!btnFocus) btnFocus = pBtnAbort; |
| 963 | } |
| 964 | // Yes |
| 965 | if (dwButtons & btnYes) |
| 966 | { |
| 967 | Button *pBtnYes = newYesButton(bounds: rcBtn); |
| 968 | AddElement(pChild: pBtnYes); |
| 969 | rcBtn.x += C4GUI_DefButton2Wdt + C4GUI_DefButton2HSpace; |
| 970 | if (!btnFocus && !fDefaultNo) btnFocus = pBtnYes; |
| 971 | } |
| 972 | // No |
| 973 | if (dwButtons & btnNo) |
| 974 | { |
| 975 | Button *pBtnNo = newNoButton(bounds: rcBtn); |
| 976 | AddElement(pChild: pBtnNo); |
| 977 | if (!btnFocus) btnFocus = pBtnNo; |
| 978 | } |
| 979 | } |
| 980 | if (btnFocus) SetFocus(pCtrl: btnFocus, fByMouse: false); |
| 981 | // resize to actually needed size |
| 982 | SetClientSize(iToWdt: GetClientRect().Wdt, iToHgt: GetClientRect().Hgt - caMain.GetHeight()); |
| 983 | } |
| 984 | |
| 985 | // ConfirmationDialog |
| 986 | |
| 987 | ConfirmationDialog::ConfirmationDialog(const char *szMessage, const char *szCaption, BaseCallbackHandler *pCB, uint32_t dwButtons, bool fSmall, Icons icoIcon) |
| 988 | : MessageDialog(szMessage, szCaption, dwButtons, icoIcon, fSmall ? MessageDialog::dsSmall : MessageDialog::dsRegular) |
| 989 | { |
| 990 | if (this->pCB = pCB) pCB->Ref(); |
| 991 | // always log confirmation messages |
| 992 | spdlog::debug(fmt: "[Cnf] {}: {}" , args&: szCaption, args&: szMessage); |
| 993 | // confirmations always get deleted on close |
| 994 | SetDelOnClose(); |
| 995 | } |
| 996 | |
| 997 | void ConfirmationDialog::OnClosed(bool fOK) |
| 998 | { |
| 999 | // confirmed only on OK |
| 1000 | BaseCallbackHandler *pStackCB = fOK ? pCB : nullptr; |
| 1001 | if (pStackCB) pStackCB->Ref(); |
| 1002 | // caution: this will usually delete the dlg (this) |
| 1003 | // so the CB-interface is backed up |
| 1004 | MessageDialog::OnClosed(fOK); |
| 1005 | if (pStackCB) |
| 1006 | { |
| 1007 | pStackCB->DoCall(pElement: nullptr); |
| 1008 | pStackCB->DeRef(); |
| 1009 | } |
| 1010 | } |
| 1011 | |
| 1012 | // ProgressDialog |
| 1013 | |
| 1014 | ProgressDialog::ProgressDialog(const char *szMessage, const char *szCaption, int32_t iMaxProgress, int32_t iInitialProgress, Icons icoIcon) |
| 1015 | : Dialog(C4GUI_ProgressDlgWdt, 100, szCaption, false) |
| 1016 | { |
| 1017 | StdStrBuf broken; |
| 1018 | rcBounds.Hgt = (std::max)(a: GetRes()->TextFont.BreakMessage(szMsg: szMessage, C4GUI_ProgressDlgWdt - 3 * C4GUI_DefDlgIndent - C4GUI_IconWdt, pOut: &broken, fCheckMarkup: true), C4GUI_IconHgt) + C4GUI_ProgressDlgVRoom; |
| 1019 | SetBounds(rcBounds); |
| 1020 | // get positions |
| 1021 | ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true); |
| 1022 | ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0, 0); |
| 1023 | C4Rect rtProgressBar = caMain.GetFromBottom(C4GUI_ProgressDlgPBHgt); |
| 1024 | // place icon |
| 1025 | C4Rect rcIcon = caMain.GetFromLeft(C4GUI_IconWdt); rcIcon.Hgt = C4GUI_IconHgt; |
| 1026 | Icon *pIcon = new Icon(rcIcon, icoIcon); AddElement(pChild: pIcon); |
| 1027 | // place message label |
| 1028 | // use text with line breaks |
| 1029 | Label *pLblMessage = new Label(broken.getData(), caMain.GetAll().GetMiddleX(), caMain.GetAll().y, ACenter, C4GUI_MessageFontClr, &GetRes()->TextFont); |
| 1030 | AddElement(pChild: pLblMessage); |
| 1031 | // place progress bar |
| 1032 | pBar = new ProgressBar(rtProgressBar, iMaxProgress); |
| 1033 | pBar->SetProgress(iInitialProgress); |
| 1034 | pBar->SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_DLGTIP_PROGRESS)); |
| 1035 | AddElement(pChild: pBar); |
| 1036 | // place abort button |
| 1037 | Button *pBtnAbort = newCancelButton(bounds: caButtonArea.GetCentered(C4GUI_DefButtonWdt, C4GUI_ButtonHgt)); |
| 1038 | AddElement(pChild: pBtnAbort); |
| 1039 | } |
| 1040 | |
| 1041 | // Some dialog wrappers in Screen class |
| 1042 | |
| 1043 | bool Screen::ShowMessage(const char *szMessage, const char *szCaption, Icons icoIcon, bool *piConfigDontShowAgainSetting) |
| 1044 | { |
| 1045 | // always log messages |
| 1046 | spdlog::debug(fmt: "[Msg] {}: {}" , args&: szCaption, args&: szMessage); |
| 1047 | if (piConfigDontShowAgainSetting && *piConfigDontShowAgainSetting) return true; |
| 1048 | #ifdef USE_CONSOLE |
| 1049 | // skip in console mode |
| 1050 | return true; |
| 1051 | #endif |
| 1052 | return ShowRemoveDlg(pDlg: new MessageDialog(szMessage, szCaption, MessageDialog::btnOK, icoIcon, MessageDialog::dsRegular, piConfigDontShowAgainSetting)); |
| 1053 | } |
| 1054 | |
| 1055 | bool Screen::ShowErrorMessage(const char *szMessage) |
| 1056 | { |
| 1057 | return ShowMessage(szMessage, szCaption: LoadResStr(id: C4ResStrTableKey::IDS_DLG_ERROR), icoIcon: Ico_Error); |
| 1058 | } |
| 1059 | |
| 1060 | bool Screen::ShowMessageModal(const char *szMessage, const char *szCaption, uint32_t dwButtons, Icons icoIcon, bool *pbConfigDontShowAgainSetting) |
| 1061 | { |
| 1062 | // always log messages |
| 1063 | spdlog::debug(fmt: "[Modal] {}: {}" , args&: szCaption, args&: szMessage); |
| 1064 | // skip if user doesn't want to see it |
| 1065 | if (pbConfigDontShowAgainSetting && *pbConfigDontShowAgainSetting) return true; |
| 1066 | // create message dlg and show modal |
| 1067 | return ShowModalDlg(pDlg: new MessageDialog(szMessage, szCaption, dwButtons, icoIcon, MessageDialog::dsRegular, pbConfigDontShowAgainSetting)); |
| 1068 | } |
| 1069 | |
| 1070 | bool Screen::ShowModalDlg(Dialog *pDlg, bool fDestruct) |
| 1071 | { |
| 1072 | #ifdef USE_CONSOLE |
| 1073 | // no modal dialogs in console build |
| 1074 | // (there's most likely no way to close them!) |
| 1075 | if (fDestruct) delete pDlg; |
| 1076 | return true; |
| 1077 | #endif |
| 1078 | // safety |
| 1079 | if (!pDlg) return false; |
| 1080 | // show it |
| 1081 | if (!pDlg->Show(pOnScreen: this, fCB: true)) { delete pDlg; return false; } |
| 1082 | // wait until it is closed |
| 1083 | bool fResult = pDlg->DoModal(); |
| 1084 | // free dlg if this class is still valid (may have been deleted in game clear) |
| 1085 | if (!IsGUIValid()) return false; |
| 1086 | if (fDestruct) delete pDlg; |
| 1087 | // return result |
| 1088 | return fResult; |
| 1089 | } |
| 1090 | |
| 1091 | bool Screen::ShowRemoveDlg(Dialog *pDlg) |
| 1092 | { |
| 1093 | // safety |
| 1094 | if (!pDlg) return false; |
| 1095 | // mark removal when done |
| 1096 | pDlg->SetDelOnClose(); |
| 1097 | // show it |
| 1098 | if (!pDlg->Show(pOnScreen: this, fCB: true)) { delete pDlg; return false; } |
| 1099 | // done, success |
| 1100 | return true; |
| 1101 | } |
| 1102 | |
| 1103 | // InputDialog |
| 1104 | |
| 1105 | InputDialog::InputDialog(const char *szMessage, const char *szCaption, Icons icoIcon, BaseInputCallback *pCB, bool fChatLayout) |
| 1106 | : Dialog(fChatLayout ? Config.Graphics.ResX * 4 / 5 : C4GUI_InputDlgWdt, fChatLayout ? C4GUI::Edit::GetDefaultEditHeight() + 2 : 100, szCaption, false), pEdit(nullptr), pChatLbl(nullptr), pCB(pCB), fChatLayout(fChatLayout) |
| 1107 | { |
| 1108 | if (fChatLayout) |
| 1109 | { |
| 1110 | // chat input layout |
| 1111 | C4GUI::ComponentAligner caChat(GetContainedClientRect(), 1, 1); |
| 1112 | // normal chatbox layout: Left chat label |
| 1113 | int32_t w = 40, h; |
| 1114 | C4GUI::GetRes()->TextFont.GetTextExtent(szText: szMessage, rsx&: w, rsy&: h, fCheckMarkup: true); |
| 1115 | pChatLbl = new C4GUI::WoodenLabel(szMessage, caChat.GetFromLeft(iWdt: w + 4), C4GUI_CaptionFontClr, &C4GUI::GetRes()->TextFont); |
| 1116 | caChat.ExpandLeft(iByWdt: 2); // undo margin |
| 1117 | rcEditBounds = caChat.GetAll(); |
| 1118 | SetCustomEdit(new Edit(rcEditBounds)); |
| 1119 | pChatLbl->SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_DLGTIP_CHAT)); |
| 1120 | AddElement(pChild: pChatLbl); |
| 1121 | } |
| 1122 | else |
| 1123 | { |
| 1124 | StdStrBuf broken; |
| 1125 | rcBounds.Hgt = (std::max)(a: GetRes()->TextFont.BreakMessage(szMsg: szMessage, C4GUI_InputDlgWdt - 3 * C4GUI_DefDlgIndent - C4GUI_IconWdt, pOut: &broken, fCheckMarkup: true), C4GUI_IconHgt) + C4GUI_InputDlgVRoom; |
| 1126 | SetBounds(rcBounds); |
| 1127 | // regular input dialog layout |
| 1128 | // get positions |
| 1129 | ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true); |
| 1130 | ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0, 0); |
| 1131 | rcEditBounds = caMain.GetFromBottom(iHgt: Edit::GetDefaultEditHeight()); |
| 1132 | // place icon |
| 1133 | C4Rect rcIcon = caMain.GetFromLeft(C4GUI_IconWdt); rcIcon.Hgt = C4GUI_IconHgt; |
| 1134 | Icon *pIcon = new Icon(rcIcon, icoIcon); AddElement(pChild: pIcon); |
| 1135 | // place message label |
| 1136 | // use text with line breaks |
| 1137 | Label *pLblMessage = new Label(broken.getData(), caMain.GetAll().GetMiddleX(), caMain.GetAll().y, ACenter, C4GUI_MessageFontClr, &GetRes()->TextFont); |
| 1138 | AddElement(pChild: pLblMessage); |
| 1139 | // place input edit |
| 1140 | SetCustomEdit(new Edit(rcEditBounds)); |
| 1141 | // place buttons |
| 1142 | C4Rect rcBtn = caButtonArea.GetCentered(iWdt: 2 * C4GUI_DefButton2Wdt + C4GUI_DefButton2HSpace, C4GUI_ButtonHgt); |
| 1143 | rcBtn.Wdt = C4GUI_DefButton2Wdt; |
| 1144 | // OK |
| 1145 | Button *pBtnOK = newOKButton(bounds: rcBtn); |
| 1146 | AddElement(pChild: pBtnOK); |
| 1147 | rcBtn.x += rcBtn.Wdt + C4GUI_DefButton2HSpace; |
| 1148 | // Cancel |
| 1149 | Button *pBtnAbort = newCancelButton(bounds: rcBtn); |
| 1150 | AddElement(pChild: pBtnAbort); |
| 1151 | rcBtn.x += rcBtn.Wdt + C4GUI_DefButton2HSpace; |
| 1152 | } |
| 1153 | // input dlg always closed in the end |
| 1154 | SetDelOnClose(); |
| 1155 | } |
| 1156 | |
| 1157 | void InputDialog::SetMaxText(int32_t iMaxLen) |
| 1158 | { |
| 1159 | pEdit->SetMaxText(iMaxLen); |
| 1160 | } |
| 1161 | |
| 1162 | void InputDialog::SetInputText(const char *szToText) |
| 1163 | { |
| 1164 | pEdit->SelectAll(); pEdit->DeleteSelection(); |
| 1165 | if (szToText) |
| 1166 | { |
| 1167 | pEdit->InsertText(text: szToText, fUser: false); |
| 1168 | pEdit->SelectAll(); |
| 1169 | } |
| 1170 | } |
| 1171 | |
| 1172 | void InputDialog::SetCustomEdit(Edit *pCustomEdit) |
| 1173 | { |
| 1174 | // del old |
| 1175 | delete pEdit; |
| 1176 | // add new |
| 1177 | pEdit = pCustomEdit; |
| 1178 | pEdit->SetBounds(rcEditBounds); |
| 1179 | if (fChatLayout) |
| 1180 | { |
| 1181 | pEdit->SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_DLGTIP_CHAT)); |
| 1182 | pChatLbl->SetClickFocusControl(pEdit); // 2do: to all, to allies, etc. |
| 1183 | } |
| 1184 | AddElement(pChild: pEdit); |
| 1185 | SetFocus(pCtrl: pEdit, fByMouse: false); |
| 1186 | } |
| 1187 | |
| 1188 | void InputDialog::OnClosed(bool fOK) |
| 1189 | { |
| 1190 | if (pCB && fOK) |
| 1191 | { |
| 1192 | pCB->OnOK(sText: StdStrBuf::MakeRef(str: pEdit->GetText())); |
| 1193 | } |
| 1194 | Dialog::OnClosed(fOK); |
| 1195 | } |
| 1196 | |
| 1197 | const char *InputDialog::GetInputText() |
| 1198 | { |
| 1199 | return pEdit->GetText(); |
| 1200 | } |
| 1201 | |
| 1202 | // InfoDialog |
| 1203 | |
| 1204 | InfoDialog::InfoDialog(const char *szCaption, int32_t iLineCount) |
| 1205 | : Dialog(C4GUI_InfoDlgWdt, GetRes()->TextFont.GetLineHeight() * iLineCount + C4GUI_InfoDlgVRoom, szCaption, false), iScroll(0), pSec1Timer(nullptr) |
| 1206 | { |
| 1207 | // timer |
| 1208 | pSec1Timer = new C4Sec1TimerCallback<InfoDialog>(this); |
| 1209 | CreateSubComponents(); |
| 1210 | } |
| 1211 | |
| 1212 | InfoDialog::InfoDialog(const char *szCaption, int iLineCount, const StdStrBuf &sText) |
| 1213 | : Dialog(C4GUI_InfoDlgWdt, GetRes()->TextFont.GetLineHeight() * iLineCount + C4GUI_InfoDlgVRoom, szCaption, false), iScroll(0), pSec1Timer(nullptr) |
| 1214 | { |
| 1215 | // ctor - init w/o timer |
| 1216 | CreateSubComponents(); |
| 1217 | // fill in initial text |
| 1218 | for (size_t i = 0; i < sText.getLength(); ++i) |
| 1219 | { |
| 1220 | size_t i0 = i; |
| 1221 | while (sText[i] != '|' && sText[i]) ++i; |
| 1222 | StdStrBuf sLine = sText.copyPart(iStart: i0, inSize: i - i0); |
| 1223 | pTextWin->AddTextLine(szText: sLine.getData(), pFont: &GetRes()->TextFont, C4GUI_MessageFontClr, fDoUpdate: false, fMakeReadableOnBlack: true); |
| 1224 | } |
| 1225 | pTextWin->UpdateHeight(); |
| 1226 | } |
| 1227 | |
| 1228 | InfoDialog::~InfoDialog() |
| 1229 | { |
| 1230 | if (pSec1Timer) pSec1Timer->Release(); |
| 1231 | } |
| 1232 | |
| 1233 | void InfoDialog::CreateSubComponents() |
| 1234 | { |
| 1235 | // get positions |
| 1236 | ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true); |
| 1237 | ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0, 0); |
| 1238 | // place info box |
| 1239 | pTextWin = new TextWindow(caMain.GetAll(), 0, 0, 0, 100, 4096, " " , true, nullptr, 0); |
| 1240 | AddElement(pChild: pTextWin); |
| 1241 | // place close button |
| 1242 | Button *pBtnClose = newDlgCloseButton(bounds: caButtonArea.GetCentered(C4GUI_DefButtonWdt, C4GUI_ButtonHgt)); |
| 1243 | AddElement(pChild: pBtnClose); pBtnClose->SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_MNU_CLOSE)); |
| 1244 | } |
| 1245 | |
| 1246 | void InfoDialog::AddLine(const char *szText) |
| 1247 | { |
| 1248 | // add line to text window |
| 1249 | if (!pTextWin) return; |
| 1250 | pTextWin->AddTextLine(szText, pFont: &GetRes()->TextFont, C4GUI_MessageFontClr, fDoUpdate: false, fMakeReadableOnBlack: true); |
| 1251 | } |
| 1252 | |
| 1253 | void InfoDialog::BeginUpdateText() |
| 1254 | { |
| 1255 | // safety |
| 1256 | if (!pTextWin) return; |
| 1257 | // backup scrolling |
| 1258 | iScroll = pTextWin->GetScrollPos(); |
| 1259 | // clear text window, so new text can be added |
| 1260 | pTextWin->ClearText(fDoUpdate: false); |
| 1261 | } |
| 1262 | |
| 1263 | void InfoDialog::EndUpdateText() |
| 1264 | { |
| 1265 | // safety |
| 1266 | if (!pTextWin) return; |
| 1267 | // update text height |
| 1268 | pTextWin->UpdateHeight(); |
| 1269 | // restore scrolling |
| 1270 | pTextWin->SetScrollPos(iScroll); |
| 1271 | } |
| 1272 | |
| 1273 | void InfoDialog::OnSec1Timer() |
| 1274 | { |
| 1275 | // always update |
| 1276 | UpdateText(); |
| 1277 | } |
| 1278 | |
| 1279 | TimedDialog::TimedDialog(uint32_t time, const char *message, const char *caption, uint32_t buttons, Icons icon, DlgSize size, bool *configDontShowAgainSetting, bool defaultNo, int32_t zOrdering) |
| 1280 | : MessageDialog{message, caption, buttons, icon, size, configDontShowAgainSetting, defaultNo, zOrdering}, time{time} |
| 1281 | { |
| 1282 | sec1Timer = new C4Sec1TimerCallback<TimedDialog>{this}; |
| 1283 | } |
| 1284 | |
| 1285 | TimedDialog::~TimedDialog() |
| 1286 | { |
| 1287 | sec1Timer->Release(); |
| 1288 | } |
| 1289 | |
| 1290 | void TimedDialog::OnSec1Timer() |
| 1291 | { |
| 1292 | if (--time == 0) |
| 1293 | { |
| 1294 | Close(fOK: false); |
| 1295 | return; |
| 1296 | } |
| 1297 | |
| 1298 | UpdateText(); |
| 1299 | } |
| 1300 | |
| 1301 | void TimedDialog::SetText(const char *message) |
| 1302 | { |
| 1303 | lblText->SetText(szText: message); |
| 1304 | |
| 1305 | ComponentAligner caMain{GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true}; |
| 1306 | caMain.ExpandTop(iByHgt: -lblText->GetHeight()); |
| 1307 | ComponentAligner caButtonArea{caMain.GetFromTop(C4GUI_ButtonAreaHgt), 0, 0}; |
| 1308 | const C4Rect &bounds{caButtonArea.GetCentered(C4GUI_DefButtonWdt, C4GUI_DefButton2HSpace)}; |
| 1309 | |
| 1310 | int32_t oldButtonY{-1}; |
| 1311 | int32_t oldButtonHeight{0}; |
| 1312 | |
| 1313 | for (Element *element{GetFirst()}; element; element = element->GetNext()) |
| 1314 | { |
| 1315 | if (element != pCloseBtn) |
| 1316 | { |
| 1317 | if (auto *btn = dynamic_cast<Button *>(element); btn) |
| 1318 | { |
| 1319 | int32_t &y{btn->GetBounds().y}; |
| 1320 | if (oldButtonY == -1) |
| 1321 | { |
| 1322 | oldButtonY = y; |
| 1323 | oldButtonHeight = btn->GetHeight(); |
| 1324 | } |
| 1325 | |
| 1326 | y = bounds.y; |
| 1327 | } |
| 1328 | } |
| 1329 | } |
| 1330 | |
| 1331 | GetClientRect().Hgt = bounds.y + oldButtonHeight + C4GUI_DefButton2HSpace; |
| 1332 | if (pTitle) |
| 1333 | { |
| 1334 | GetBounds().Hgt = GetClientRect().Hgt + pTitle->GetHeight(); |
| 1335 | } |
| 1336 | UpdateSize(); |
| 1337 | } |
| 1338 | |
| 1339 | void CloseButton::OnPress() |
| 1340 | { |
| 1341 | Dialog *pDlg; |
| 1342 | if ((pDlg = GetDlg())) |
| 1343 | { |
| 1344 | pDlg->UserClose(fOK: fCloseResult); |
| 1345 | } |
| 1346 | } |
| 1347 | |
| 1348 | void CloseIconButton::OnPress() |
| 1349 | { |
| 1350 | Dialog *pDlg; |
| 1351 | if ((pDlg = GetDlg())) |
| 1352 | { |
| 1353 | pDlg->UserClose(fOK: fCloseResult); |
| 1354 | } |
| 1355 | } |
| 1356 | } // end of namespace |
| 1357 | |